Когда нужно проверить начало строки, в голову обычно сразу приходит LIKE.
Например:
SELECT *
FROM orders
WHERE status LIKE 'ship%';
Такой запрос ищет строки, где status начинается с ship.
Но в PostgreSQL 11+ есть более прямой и читаемый вариант — функция starts_with.
Она отвечает ровно на один вопрос:
Начинается ли эта строка с такого-то префикса?
Пример:
SELECT starts_with('/api/orders', '/api/');
Результат:
true
Функция особенно удобна для маршрутов, кодов, артикулов, email-префиксов, статусов, технических имён и любых случаев, где префикс — это обычный текст, а не шаблон.
Главное отличие от LIKE в том, что starts_with воспринимает префикс буквально. Символы % и _ для неё не имеют особого смысла. Это не шаблон, а обычная строка.
Базовый синтаксис
У функции два аргумента:
starts_with(str, prefix)
Первый аргумент — строка, которую проверяем.
Второй аргумент — префикс, с которого она должна начинаться.
Пример:
SELECT starts_with('/api/orders', '/api/') AS is_api_path;
Результат:
is_api_path
-----------
true
Ещё пример:
SELECT starts_with('/admin/users', '/api/') AS is_api_path;
Результат:
is_api_path
-----------
false
Функция возвращает значение типа boolean: true, false или NULL, если один из аргументов равен NULL.
Поэтому её удобно использовать в WHERE:
SELECT id, status, amount
FROM orders
WHERE starts_with(status, 'ship');
Такой запрос вернёт заказы со статусами вроде shipped, shipping, ship_ready.
Почему starts_with читается лучше, чем LIKE
Сравним два варианта:
SELECT *
FROM orders
WHERE status LIKE 'ship%';
И:
SELECT *
FROM orders
WHERE starts_with(status, 'ship');
Оба запроса по смыслу ищут строки, которые начинаются с ship.
Но второй вариант читается почти как обычная фраза: status начинается с ship.
В простых случаях разница небольшая. Но чем сложнее префикс, тем заметнее польза.
Например, проверим путь API:
SELECT *
FROM access_logs
WHERE starts_with(path, '/api/v1/orders/');
Здесь не нужно вспоминать правила LIKE, думать про %, _ и экранирование. Видно сразу: путь должен начинаться с конкретного текста.
Главная разница: текст против шаблона
LIKE работает с шаблоном.
В шаблоне есть специальные символы:
% означает любое количество любых символов;
_ означает один любой символ.
Например:
SELECT 'ship_1' LIKE 'ship_';
Результат:
true
Потому что _ в LIKE — это не символ подчёркивания, а один любой символ.
А starts_with воспринимает второй аргумент как обычный текст.
SELECT starts_with('ship_1', 'ship_');
Результат:
true
Но здесь ship_ — это именно буквы s, h, i, p и символ _, а не шаблон.
Это особенно важно, если префикс приходит из приложения или пользовательского ввода.
Пример с % и _
Допустим, в статусах или промокодах есть строки, которые начинаются с 50%.
Например:
50%_off_winter
50%_off_spring
Если использовать LIKE, символ % нужно экранировать, иначе PostgreSQL воспримет его как часть шаблона.
SELECT *
FROM orders
WHERE status LIKE '50\%%';
Такой запрос уже выглядит менее приятно. Нужно помнить, что первый % — это символ процента в данных, а второй % — это подстановочный символ LIKE.
С starts_with проще:
SELECT *
FROM orders
WHERE starts_with(status, '50%');
Здесь 50% — обычный текст. Ничего экранировать не нужно.
Это снижает риск ошибок. Особенно в коде, где префикс собирается динамически.
Проверка маршрутов в логах
Один из самых понятных сценариев — логи запросов.
Допустим, есть таблица:
CREATE TABLE access_logs (
id bigserial PRIMARY KEY,
path text NOT NULL,
status_code integer NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
Нужно выбрать все запросы к API:
SELECT id, path, status_code, created_at
FROM access_logs
WHERE starts_with(path, '/api/');
Можно выбрать только старую версию API:
SELECT id, path, status_code, created_at
FROM access_logs
WHERE starts_with(path, '/api/v1/');
Или все запросы к админке:
SELECT id, path, status_code, created_at
FROM access_logs
WHERE starts_with(path, '/admin/');
Такой запрос легко читать даже человеку, который только начинает изучать SQL.
Проверка кодов и артикулов
Префикс часто используется в кодах.
Например:
RU-001
RU-002
KZ-115
VN-220
Если в таблице товаров есть артикул:
CREATE TABLE products (
id bigserial PRIMARY KEY,
sku text NOT NULL,
name text NOT NULL,
price numeric(12, 2) NOT NULL
);
Можно найти все товары с префиксом RU-:
SELECT id, sku, name, price
FROM products
WHERE starts_with(sku, 'RU-');
Или товары из другой линейки:
SELECT id, sku, name, price
FROM products
WHERE starts_with(sku, 'PREMIUM-');
В таких задачах starts_with хорошо передаёт намерение: нас интересует именно начало строки.
Чувствительность к регистру
starts_with чувствителен к регистру.
Это значит, что API и api — разные строки.
SELECT starts_with('API/orders', 'api');
Результат:
false
А так будет true:
SELECT starts_with('API/orders', 'API');
Если нужно сравнение без учёта регистра, приведите обе стороны к одному регистру.
Например, через lower:
SELECT *
FROM users
WHERE starts_with(lower(email), 'admin@');
Такой запрос найдёт и admin@example.com, и Admin@example.com, и ADMIN@example.com.
Но у этого подхода есть важное последствие для индексов: функция над колонкой может помешать использовать обычный индекс по этой колонке. Для больших таблиц иногда нужен отдельный индекс по выражению.
CREATE INDEX idx_users_email_lower
ON users (lower(email));
Тогда запрос с lower(email) сможет получить шанс на нормальный план.
Что происходит с пустым префиксом
Любая строка начинается с пустой строки.
Поэтому такой запрос вернёт true:
SELECT starts_with('abc', '');
Результат:
true
Это логично, но в реальном коде иногда становится неожиданностью.
Если префикс приходит из формы поиска, пользователь может отправить пустое значение. Тогда условие:
WHERE starts_with(name, '')
подойдёт почти ко всем строкам, где name не равен NULL.
Если такое поведение нежелательно, проверяйте префикс отдельно на уровне приложения или в SQL.
Например:
SELECT *
FROM products
WHERE length('RU-') > 0
AND starts_with(sku, 'RU-');
В реальном запросе вместо 'RU-' обычно будет параметр приложения.
Что происходит с NULL
Если один из аргументов равен SQL-NULL, результат тоже будет NULL.
SELECT starts_with(NULL, 'api');
Результат:
null
И так тоже:
SELECT starts_with('/api/orders', NULL);
Результат:
null
Это нормальное поведение SQL: неизвестное значение даёт неизвестный результат.
В WHERE это обычно не видно, потому что строки, где условие вернуло NULL, отфильтровываются так же, как строки с false.
Например:
SELECT *
FROM access_logs
WHERE starts_with(path, '/api/');
Если path равен NULL, строка не попадёт в результат.
Но в SELECT, CASE или бизнес-логике разница между false и NULL может быть важной.
Если вам нужен строгий результат true или false, используйте COALESCE:
SELECT
path,
COALESCE(starts_with(path, '/api/'), false) AS is_api_path
FROM access_logs;
Теперь NULL превратится в false.
starts_with в CASE
Так как функция возвращает boolean, её удобно использовать в CASE.
Например, разметим запросы по разделам сайта:
SELECT
id,
path,
CASE
WHEN starts_with(path, '/api/') THEN 'api'
WHEN starts_with(path, '/admin/') THEN 'admin'
WHEN starts_with(path, '/static/') THEN 'static'
ELSE 'other'
END AS path_group
FROM access_logs;
Так можно быстро собрать понятную группировку для отчёта.
Например, посчитать количество запросов по разделам:
SELECT
CASE
WHEN starts_with(path, '/api/') THEN 'api'
WHEN starts_with(path, '/admin/') THEN 'admin'
WHEN starts_with(path, '/static/') THEN 'static'
ELSE 'other'
END AS path_group,
count(*) AS request_count
FROM access_logs
GROUP BY path_group
ORDER BY request_count DESC;
Это хороший пример, где функция делает запрос выразительным: сразу видно, как именно мы классифицируем строки.
starts_with в CHECK-ограничении
Функцию можно использовать не только в выборках, но и в ограничениях.
Например, хотим, чтобы все артикулы начинались с SKU-:
CREATE TABLE items (
id bigserial PRIMARY KEY,
sku text NOT NULL,
name text NOT NULL,
CONSTRAINT items_sku_prefix_check
CHECK (starts_with(sku, 'SKU-'))
);
Теперь PostgreSQL не даст вставить строку с неправильным артикулом:
INSERT INTO items (sku, name)
VALUES ('ABC-001', 'Keyboard');
Такое ограничение полезно, когда формат значения должен соблюдаться всегда, а не только в одном запросе.
Производительность и индексы
На маленьких таблицах разницы обычно не видно: starts_with, LIKE и другие варианты работают быстро.
На больших таблицах нужно думать об индексе.
Для префиксного поиска часто используют LIKE 'prefix%' с подходящим индексом.
Например:
CREATE INDEX idx_orders_status_pattern
ON orders (status text_pattern_ops);
И запрос:
SELECT *
FROM orders
WHERE status LIKE 'ship%';
Такой индекс помогает PostgreSQL искать строки по началу текста.
Почему не всегда достаточно обычного индекса?
Обычный B-tree индекс по текстовой колонке зависит от правил сортировки. В некоторых локалях PostgreSQL не сможет эффективно использовать его для LIKE 'x%'. Класс операторов text_pattern_ops специально предназначен для шаблонов с префиксом.
Проверять нужно не на глаз, а через EXPLAIN.
EXPLAIN
SELECT *
FROM orders
WHERE status LIKE 'ship%';
Если видите Index Scan или Bitmap Index Scan, индекс используется. Если видите Seq Scan, PostgreSQL читает таблицу последовательно.
А что с индексом для starts_with
Важный практический момент: starts_with не всегда оптимизируется так же очевидно, как LIKE 'prefix%'.
Поэтому если у вас большая таблица и запрос критичен по скорости, обязательно проверьте план выполнения.
EXPLAIN
SELECT *
FROM orders
WHERE starts_with(status, 'ship');
Если план показывает последовательное чтение всей таблицы, а вам нужен быстрый поиск по префиксу, можно использовать вариант с LIKE и индексом text_pattern_ops.
SELECT *
FROM orders
WHERE status LIKE 'ship%';
Получается такое правило:
- для читаемости и безопасной проверки буквального префикса удобен
starts_with;
- для горячих запросов на больших таблицах проверяйте план;
- если индекс лучше подхватывается через
LIKE 'x%', используйте LIKE в производительном участке.
В SQL часто приходится выбирать не только по красоте, но и по плану выполнения.
Как совместить читаемость и скорость
Иногда можно оставить starts_with в менее критичных местах: в CHECK, отчётах, небольших справочниках, админских запросах.
А для нагруженного поиска использовать LIKE с правильно подготовленным префиксом.
Например, если префикс задан разработчиком и не содержит пользовательских спецсимволов:
SELECT *
FROM access_logs
WHERE path LIKE '/api/%';
Если же префикс приходит от пользователя и может содержать % или _, нужно аккуратно экранировать его для LIKE.
В таких случаях starts_with проще и безопаснее:
SELECT *
FROM access_logs
WHERE starts_with(path, '50%');
Главное — не принимать решение вслепую. Для больших таблиц смотрите EXPLAIN.
Аналог в MySQL
В MySQL нет функции starts_with с таким же именем.
Чаще всего используют LIKE:
SELECT id, status
FROM orders
WHERE status LIKE 'ship%';
Или функцию LEFT:
SELECT id, status
FROM orders
WHERE LEFT(status, 4) = 'ship';
Вариант с LEFT похож по смыслу на starts_with: мы берём первые 4 символа строки и сравниваем с ship.
Но есть важная особенность MySQL: чувствительность к регистру зависит от collation колонки.
Например, при collation с суффиксом _ci сравнение обычно нечувствительно к регистру. Тогда ship% может совпасть и с Shipped.
Если нужно строгое сравнение с учётом регистра, используют бинарное сравнение или подходящую collation.
SELECT id, status
FROM orders
WHERE BINARY status LIKE 'ship%';
При переносе запроса из PostgreSQL в MySQL это важно проверить. В PostgreSQL starts_with регистрозависим, а в MySQL результат может зависеть от настроек колонки.
Аналог в ClickHouse
В ClickHouse есть функция startsWith.
Она очень близка по смыслу к PostgreSQL starts_with.
SELECT id, status
FROM orders
WHERE startsWith(status, 'ship');
Название отличается стилем написания, но идея та же: проверить, начинается ли строка с указанного префикса.
При переносе запросов всё равно стоит отдельно проверить поведение на:
NULL
''
Ship
ship
50%_off
Именно на таких значениях обычно всплывают различия между СУБД.
starts_with или LIKE: что выбрать
Если нужно просто проверить начало строки и префикс должен восприниматься буквально, берите starts_with.
Хорошие случаи:
WHERE starts_with(path, '/api/')
WHERE starts_with(sku, 'RU-')
WHERE starts_with(email, 'admin@')
Если нужен именно шаблон, где % и _ должны работать как специальные символы, используйте LIKE.
Например:
WHERE status LIKE 'ship%'
Если запрос должен быть максимально быстрым на большой таблице, проверьте оба варианта через EXPLAIN и подготовьте подходящий индекс.
Частые ошибки
Первая ошибка — забыть, что starts_with чувствителен к регистру.
SELECT starts_with('Shipped', 'ship');
Результат:
false
Если нужен поиск без учёта регистра, нормализуйте обе стороны:
SELECT starts_with(lower('Shipped'), 'ship');
Вторая ошибка — ждать, что NULL превратится в false.
SELECT starts_with(NULL, 'ship');
Результат:
null
Если нужен именно false, используйте COALESCE:
SELECT COALESCE(starts_with(NULL, 'ship'), false);
Третья ошибка — думать, что LIKE и starts_with полностью одинаковы.
Так не всегда. В LIKE символы % и _ особенные, а в starts_with это обычные символы.
Четвёртая ошибка — не проверять индекс на большой таблице.
Красивый запрос может быть медленным, если PostgreSQL читает всю таблицу. Используйте EXPLAIN.
Пятая ошибка — забыть про пустой префикс.
SELECT starts_with('abc', '');
Результат:
true
Если пустой префикс не должен проходить, проверяйте его отдельно.
Главное
starts_with(str, prefix) проверяет, начинается ли строка str с префикса prefix.
В PostgreSQL эта функция доступна с версии 11 и возвращает boolean.
В отличие от LIKE, префикс в starts_with — обычный текст, а не шаблон. Символы % и _ не нужно экранировать.
starts_with чувствителен к регистру. Для поиска без учёта регистра используйте lower или другой подход к нормализации.
Если один из аргументов равен NULL, результат тоже будет NULL. Для строгого true или false оборачивайте результат в COALESCE.
Для больших таблиц проверяйте план через EXPLAIN. Иногда для быстрого префиксного поиска лучше использовать LIKE 'x%' вместе с индексом text_pattern_ops.
starts_with хорош тем, что делает намерение запроса очевидным: мы не пишем шаблон, не играем с подстановками, не экранируем лишние символы, а просто проверяем начало строки. Для новичка и для команды, которая потом будет читать этот SQL, это часто уже большая победа.
Когда нужно проверить начало строки, в голову обычно сразу приходит
LIKE.Например:
SELECT * FROM orders WHERE status LIKE 'ship%';Такой запрос ищет строки, где
statusначинается сship.Но в PostgreSQL 11+ есть более прямой и читаемый вариант — функция
starts_with.Она отвечает ровно на один вопрос:
Пример:
SELECT starts_with('/api/orders', '/api/');Результат:
Функция особенно удобна для маршрутов, кодов, артикулов, email-префиксов, статусов, технических имён и любых случаев, где префикс — это обычный текст, а не шаблон.
Главное отличие от
LIKEв том, чтоstarts_withвоспринимает префикс буквально. Символы%и_для неё не имеют особого смысла. Это не шаблон, а обычная строка.Базовый синтаксис
У функции два аргумента:
Первый аргумент — строка, которую проверяем.
Второй аргумент — префикс, с которого она должна начинаться.
Пример:
SELECT starts_with('/api/orders', '/api/') AS is_api_path;Результат:
Ещё пример:
SELECT starts_with('/admin/users', '/api/') AS is_api_path;Результат:
Функция возвращает значение типа
boolean:true,falseилиNULL, если один из аргументов равенNULL.Поэтому её удобно использовать в
WHERE:SELECT id, status, amount FROM orders WHERE starts_with(status, 'ship');Такой запрос вернёт заказы со статусами вроде
shipped,shipping,ship_ready.Почему
starts_withчитается лучше, чемLIKEСравним два варианта:
SELECT * FROM orders WHERE status LIKE 'ship%';И:
SELECT * FROM orders WHERE starts_with(status, 'ship');Оба запроса по смыслу ищут строки, которые начинаются с
ship.Но второй вариант читается почти как обычная фраза:
statusначинается сship.В простых случаях разница небольшая. Но чем сложнее префикс, тем заметнее польза.
Например, проверим путь API:
SELECT * FROM access_logs WHERE starts_with(path, '/api/v1/orders/');Здесь не нужно вспоминать правила
LIKE, думать про%,_и экранирование. Видно сразу: путь должен начинаться с конкретного текста.Главная разница: текст против шаблона
LIKEработает с шаблоном.В шаблоне есть специальные символы:
%означает любое количество любых символов;_означает один любой символ.Например:
SELECT 'ship_1' LIKE 'ship_';Результат:
Потому что
_вLIKE— это не символ подчёркивания, а один любой символ.А
starts_withвоспринимает второй аргумент как обычный текст.SELECT starts_with('ship_1', 'ship_');Результат:
Но здесь
ship_— это именно буквыs,h,i,pи символ_, а не шаблон.Это особенно важно, если префикс приходит из приложения или пользовательского ввода.
Пример с
%и_Допустим, в статусах или промокодах есть строки, которые начинаются с
50%.Например:
Если использовать
LIKE, символ%нужно экранировать, иначе PostgreSQL воспримет его как часть шаблона.SELECT * FROM orders WHERE status LIKE '50\%%';Такой запрос уже выглядит менее приятно. Нужно помнить, что первый
%— это символ процента в данных, а второй%— это подстановочный символLIKE.С
starts_withпроще:SELECT * FROM orders WHERE starts_with(status, '50%');Здесь
50%— обычный текст. Ничего экранировать не нужно.Это снижает риск ошибок. Особенно в коде, где префикс собирается динамически.
Проверка маршрутов в логах
Один из самых понятных сценариев — логи запросов.
Допустим, есть таблица:
CREATE TABLE access_logs ( id bigserial PRIMARY KEY, path text NOT NULL, status_code integer NOT NULL, created_at timestamptz NOT NULL DEFAULT now() );Нужно выбрать все запросы к API:
SELECT id, path, status_code, created_at FROM access_logs WHERE starts_with(path, '/api/');Можно выбрать только старую версию API:
SELECT id, path, status_code, created_at FROM access_logs WHERE starts_with(path, '/api/v1/');Или все запросы к админке:
SELECT id, path, status_code, created_at FROM access_logs WHERE starts_with(path, '/admin/');Такой запрос легко читать даже человеку, который только начинает изучать SQL.
Проверка кодов и артикулов
Префикс часто используется в кодах.
Например:
Если в таблице товаров есть артикул:
CREATE TABLE products ( id bigserial PRIMARY KEY, sku text NOT NULL, name text NOT NULL, price numeric(12, 2) NOT NULL );Можно найти все товары с префиксом
RU-:SELECT id, sku, name, price FROM products WHERE starts_with(sku, 'RU-');Или товары из другой линейки:
SELECT id, sku, name, price FROM products WHERE starts_with(sku, 'PREMIUM-');В таких задачах
starts_withхорошо передаёт намерение: нас интересует именно начало строки.Чувствительность к регистру
starts_withчувствителен к регистру.Это значит, что
APIиapi— разные строки.SELECT starts_with('API/orders', 'api');Результат:
А так будет
true:SELECT starts_with('API/orders', 'API');Если нужно сравнение без учёта регистра, приведите обе стороны к одному регистру.
Например, через
lower:SELECT * FROM users WHERE starts_with(lower(email), 'admin@');Такой запрос найдёт и
admin@example.com, иAdmin@example.com, иADMIN@example.com.Но у этого подхода есть важное последствие для индексов: функция над колонкой может помешать использовать обычный индекс по этой колонке. Для больших таблиц иногда нужен отдельный индекс по выражению.
CREATE INDEX idx_users_email_lower ON users (lower(email));Тогда запрос с
lower(email)сможет получить шанс на нормальный план.Что происходит с пустым префиксом
Любая строка начинается с пустой строки.
Поэтому такой запрос вернёт
true:SELECT starts_with('abc', '');Результат:
Это логично, но в реальном коде иногда становится неожиданностью.
Если префикс приходит из формы поиска, пользователь может отправить пустое значение. Тогда условие:
WHERE starts_with(name, '')подойдёт почти ко всем строкам, где
nameне равенNULL.Если такое поведение нежелательно, проверяйте префикс отдельно на уровне приложения или в SQL.
Например:
SELECT * FROM products WHERE length('RU-') > 0 AND starts_with(sku, 'RU-');В реальном запросе вместо
'RU-'обычно будет параметр приложения.Что происходит с
NULLЕсли один из аргументов равен SQL-
NULL, результат тоже будетNULL.SELECT starts_with(NULL, 'api');Результат:
И так тоже:
SELECT starts_with('/api/orders', NULL);Результат:
Это нормальное поведение SQL: неизвестное значение даёт неизвестный результат.
В
WHEREэто обычно не видно, потому что строки, где условие вернулоNULL, отфильтровываются так же, как строки сfalse.Например:
SELECT * FROM access_logs WHERE starts_with(path, '/api/');Если
pathравенNULL, строка не попадёт в результат.Но в
SELECT,CASEили бизнес-логике разница междуfalseиNULLможет быть важной.Если вам нужен строгий результат
trueилиfalse, используйтеCOALESCE:SELECT path, COALESCE(starts_with(path, '/api/'), false) AS is_api_path FROM access_logs;Теперь
NULLпревратится вfalse.starts_withвCASEТак как функция возвращает
boolean, её удобно использовать вCASE.Например, разметим запросы по разделам сайта:
SELECT id, path, CASE WHEN starts_with(path, '/api/') THEN 'api' WHEN starts_with(path, '/admin/') THEN 'admin' WHEN starts_with(path, '/static/') THEN 'static' ELSE 'other' END AS path_group FROM access_logs;Так можно быстро собрать понятную группировку для отчёта.
Например, посчитать количество запросов по разделам:
SELECT CASE WHEN starts_with(path, '/api/') THEN 'api' WHEN starts_with(path, '/admin/') THEN 'admin' WHEN starts_with(path, '/static/') THEN 'static' ELSE 'other' END AS path_group, count(*) AS request_count FROM access_logs GROUP BY path_group ORDER BY request_count DESC;Это хороший пример, где функция делает запрос выразительным: сразу видно, как именно мы классифицируем строки.
starts_withвCHECK-ограниченииФункцию можно использовать не только в выборках, но и в ограничениях.
Например, хотим, чтобы все артикулы начинались с
SKU-:CREATE TABLE items ( id bigserial PRIMARY KEY, sku text NOT NULL, name text NOT NULL, CONSTRAINT items_sku_prefix_check CHECK (starts_with(sku, 'SKU-')) );Теперь PostgreSQL не даст вставить строку с неправильным артикулом:
INSERT INTO items (sku, name) VALUES ('ABC-001', 'Keyboard');Такое ограничение полезно, когда формат значения должен соблюдаться всегда, а не только в одном запросе.
Производительность и индексы
На маленьких таблицах разницы обычно не видно:
starts_with,LIKEи другие варианты работают быстро.На больших таблицах нужно думать об индексе.
Для префиксного поиска часто используют
LIKE 'prefix%'с подходящим индексом.Например:
CREATE INDEX idx_orders_status_pattern ON orders (status text_pattern_ops);И запрос:
SELECT * FROM orders WHERE status LIKE 'ship%';Такой индекс помогает PostgreSQL искать строки по началу текста.
Почему не всегда достаточно обычного индекса?
Обычный B-tree индекс по текстовой колонке зависит от правил сортировки. В некоторых локалях PostgreSQL не сможет эффективно использовать его для
LIKE 'x%'. Класс операторовtext_pattern_opsспециально предназначен для шаблонов с префиксом.Проверять нужно не на глаз, а через
EXPLAIN.EXPLAIN SELECT * FROM orders WHERE status LIKE 'ship%';Если видите
Index ScanилиBitmap Index Scan, индекс используется. Если видитеSeq Scan, PostgreSQL читает таблицу последовательно.А что с индексом для
starts_withВажный практический момент:
starts_withне всегда оптимизируется так же очевидно, какLIKE 'prefix%'.Поэтому если у вас большая таблица и запрос критичен по скорости, обязательно проверьте план выполнения.
EXPLAIN SELECT * FROM orders WHERE starts_with(status, 'ship');Если план показывает последовательное чтение всей таблицы, а вам нужен быстрый поиск по префиксу, можно использовать вариант с
LIKEи индексомtext_pattern_ops.SELECT * FROM orders WHERE status LIKE 'ship%';Получается такое правило:
starts_with;LIKE 'x%', используйтеLIKEв производительном участке.В SQL часто приходится выбирать не только по красоте, но и по плану выполнения.
Как совместить читаемость и скорость
Иногда можно оставить
starts_withв менее критичных местах: вCHECK, отчётах, небольших справочниках, админских запросах.А для нагруженного поиска использовать
LIKEс правильно подготовленным префиксом.Например, если префикс задан разработчиком и не содержит пользовательских спецсимволов:
SELECT * FROM access_logs WHERE path LIKE '/api/%';Если же префикс приходит от пользователя и может содержать
%или_, нужно аккуратно экранировать его дляLIKE.В таких случаях
starts_withпроще и безопаснее:SELECT * FROM access_logs WHERE starts_with(path, '50%');Главное — не принимать решение вслепую. Для больших таблиц смотрите
EXPLAIN.Аналог в MySQL
В MySQL нет функции
starts_withс таким же именем.Чаще всего используют
LIKE:SELECT id, status FROM orders WHERE status LIKE 'ship%';Или функцию
LEFT:SELECT id, status FROM orders WHERE LEFT(status, 4) = 'ship';Вариант с
LEFTпохож по смыслу наstarts_with: мы берём первые 4 символа строки и сравниваем сship.Но есть важная особенность MySQL: чувствительность к регистру зависит от collation колонки.
Например, при collation с суффиксом
_ciсравнение обычно нечувствительно к регистру. Тогдаship%может совпасть и сShipped.Если нужно строгое сравнение с учётом регистра, используют бинарное сравнение или подходящую collation.
SELECT id, status FROM orders WHERE BINARY status LIKE 'ship%';При переносе запроса из PostgreSQL в MySQL это важно проверить. В PostgreSQL
starts_withрегистрозависим, а в MySQL результат может зависеть от настроек колонки.Аналог в ClickHouse
В ClickHouse есть функция
startsWith.Она очень близка по смыслу к PostgreSQL
starts_with.SELECT id, status FROM orders WHERE startsWith(status, 'ship');Название отличается стилем написания, но идея та же: проверить, начинается ли строка с указанного префикса.
При переносе запросов всё равно стоит отдельно проверить поведение на:
Именно на таких значениях обычно всплывают различия между СУБД.
starts_withилиLIKE: что выбратьЕсли нужно просто проверить начало строки и префикс должен восприниматься буквально, берите
starts_with.Хорошие случаи:
WHERE starts_with(path, '/api/')WHERE starts_with(sku, 'RU-')WHERE starts_with(email, 'admin@')Если нужен именно шаблон, где
%и_должны работать как специальные символы, используйтеLIKE.Например:
WHERE status LIKE 'ship%'Если запрос должен быть максимально быстрым на большой таблице, проверьте оба варианта через
EXPLAINи подготовьте подходящий индекс.Частые ошибки
Первая ошибка — забыть, что
starts_withчувствителен к регистру.SELECT starts_with('Shipped', 'ship');Результат:
Если нужен поиск без учёта регистра, нормализуйте обе стороны:
SELECT starts_with(lower('Shipped'), 'ship');Вторая ошибка — ждать, что
NULLпревратится вfalse.SELECT starts_with(NULL, 'ship');Результат:
Если нужен именно
false, используйтеCOALESCE:SELECT COALESCE(starts_with(NULL, 'ship'), false);Третья ошибка — думать, что
LIKEиstarts_withполностью одинаковы.Так не всегда. В
LIKEсимволы%и_особенные, а вstarts_withэто обычные символы.Четвёртая ошибка — не проверять индекс на большой таблице.
Красивый запрос может быть медленным, если PostgreSQL читает всю таблицу. Используйте
EXPLAIN.Пятая ошибка — забыть про пустой префикс.
SELECT starts_with('abc', '');Результат:
Если пустой префикс не должен проходить, проверяйте его отдельно.
Главное
starts_with(str, prefix)проверяет, начинается ли строкаstrс префиксаprefix.В PostgreSQL эта функция доступна с версии 11 и возвращает
boolean.В отличие от
LIKE, префикс вstarts_with— обычный текст, а не шаблон. Символы%и_не нужно экранировать.starts_withчувствителен к регистру. Для поиска без учёта регистра используйтеlowerили другой подход к нормализации.Если один из аргументов равен
NULL, результат тоже будетNULL. Для строгогоtrueилиfalseоборачивайте результат вCOALESCE.Для больших таблиц проверяйте план через
EXPLAIN. Иногда для быстрого префиксного поиска лучше использоватьLIKE 'x%'вместе с индексомtext_pattern_ops.starts_withхорош тем, что делает намерение запроса очевидным: мы не пишем шаблон, не играем с подстановками, не экранируем лишние символы, а просто проверяем начало строки. Для новичка и для команды, которая потом будет читать этот SQL, это часто уже большая победа.