15. Работа со строками
🧭 Введение: почему строки ломают отчёты чаще, чем кажется
Числа и даты обычно предсказуемы, а вот строки в базе часто «грязные»:
лишние пробелы, разный регистр, опечатки, тестовые значения, неполные данные.
лишние пробелы, разный регистр, опечатки, тестовые значения, неполные данные.
Из-за этого фильтры и сегменты начинают вести себя странно:
похожие значения не совпадают, поиск ничего не находит, метрики расходятся.
похожие значения не совпадают, поиск ничего не находит, метрики расходятся.
Чтобы этого избежать, нужно уверенно владеть базовыми строковыми функциями:
CONCAT, LOWER/UPPER, LENGTH, TRIM, LIKE.💡 Совет:
Любую строковую задачу сначала формулируйте как 2 шага:
- нормализовать значение, 2) сравнить/показать его по правилу.
✅ Вывод:
Работа со строками — это про качество данных и корректную фильтрацию, а не про «косметику запроса».
⚠️ Проблема -> решение
Типичная проблема:
- строки сравниваются «как есть»,
- не учитываются пробелы и регистр,
- поиск строится только на
LIKE '%...%'без понимания последствий.
Последствия:
- потерянные записи в отчётах,
- некорректные сегменты для CRM,
- медленные запросы на больших таблицах.
Решение:
- нормализовать строки перед сравнением (
TRIM,LOWER), - выбирать правильный тип проверки (
=илиLIKE), - контролировать
NULLи пограничные случаи.
🟢 Если совсем просто:
Сначала чистим строку, потом сравниваем, потом проверяем производительность.
🎯 Как понять, что этап прошёл успешно:
Вы можете объяснить, почему строка попала или не попала в фильтр.
🛠️ Чем помогает и как работает
Строковые функции нужны почти в каждом прикладном SQL:
- формирование полного имени/адреса;
- нормализация email и тегов;
- валидация длины поля;
- поиск по части слова;
- очистка импортированных данных.
🟢 Если совсем просто:
Функции обработки строк приводят данные к единому виду, чтобы сравнение было честным.
🎯 Как понять, что этап прошёл успешно:
Вы умеете последовательно применить
TRIM -> LOWER -> сравнение.Чем помогает:
CONCATсобирает текст из нескольких полей;LOWER/UPPERрешают проблему регистра;LENGTHпроверяет полноту/качество строки;TRIMубирает лишние пробелы;LIKEдаёт шаблонный поиск.
Как это работает:
- Шаг 1: выбираем поле и цель (показ, фильтр, проверка качества).
- Шаг 2: нормализуем строку при необходимости.
- Шаг 3: применяем сравнение или шаблон поиска.
- Шаг 4: отдельно обрабатываем
NULL. - Шаг 5: проверяем результат на реальных кейсах.
✅ Вывод:
Базовые строковые функции закрывают большую часть практических задач по текстовым данным.
📚 Ключевые термины (простыми словами)
Перед практикой синхронизируем словарь.
🟢 Если совсем просто:
Термины ниже помогают не путать «как показать строку» и «как правильно её сравнить».
🎯 Как понять, что этап прошёл успешно:
Вы понимаете, какая функция нужна для сборки, очистки, длины и поиска.
- Concatenation — склейка нескольких строк в одну.
- Case normalization — приведение к единому регистру (
LOWER/UPPER). - Length check — проверка длины строки (
LENGTH). - Whitespace cleanup — очистка пробелов (
TRIM). - Pattern matching — поиск по шаблону (
LIKE). - Wildcard
%— «любая последовательность символов». - Wildcard
_— «ровно один символ». - Case-insensitive search — поиск без учёта регистра (
ILIKEв PostgreSQL).
✅ Вывод:
Эти термины покрывают базовый словарь темы «Работа со строками».
🔗 1. CONCAT: собираем строки из полей
CONCAT используется, когда нужно собрать читабельный текст из нескольких колонок:ФИО, полный адрес, подпись записи для UI.
🟢 Если совсем просто:
CONCAT склеивает текстовые части в одну строку.🎯 Как понять, что этап прошёл успешно:
Вы получаете строку в нужном формате даже при частично пустых данных.
Назначение:
Формировать вычисляемые текстовые поля без постобработки в приложении.
Простыми словами:
Берём куски текста и соединяем их в заданном порядке.
Для новичка:
Чаще удобнее явно добавлять разделители (
' ', ', ', ' - ').
В PostgreSQL CONCAT_WS(delim, ...) пропускает NULL и не добавляет лишние разделители.Аналогия:
Это как собрать подпись из имени, роли и города.
Пример:
SELECT u.id, CONCAT(u.last_name, ' ', u.first_name) AS full_name, CONCAT('ID-', u.id) AS public_labelFROM users u;Важно про
Если хотите предсказуемый результат, используйте
NULL:
В разных СУБД поведение склейки с NULL отличается.Если хотите предсказуемый результат, используйте
CONCAT_WS (PostgreSQL) или COALESCE.Пример безопасной склейки:
SELECT u.id, CONCAT_WS(' ', u.last_name, u.first_name) AS full_name_pg, CONCAT(COALESCE(u.last_name, ''), ' ', COALESCE(u.first_name, '')) AS full_name_portableFROM users u;🔎 Как это происходит на практике:
- Контекст: таблица пользователей в админке.
- Действия: формируют
full_nameи короткую метку. - Результат: фронтенд получает готовые человекочитаемые поля.
Характеристики:
- повышает читаемость API-ответа;
- снижает объём ручной склейки на клиенте;
- требует аккуратного обращения с
NULL.
Когда использовать:
Когда нужно собрать строку из нескольких текстовых компонентов.
✅ Вывод:
CONCAT полезен для представления данных, но формат лучше фиксировать явно.🔡 2. LOWER / UPPER: нормализация регистра
Регистр часто мешает корректным сравнениям:
User@Mail.com, user@mail.com, USER@MAIL.COM могут оказаться логически одним значением.🟢 Если совсем просто:
LOWER/UPPER приводят строку к одному регистру перед сравнением.🎯 Как понять, что этап прошёл успешно:
Поиск и фильтрация не зависят от случайного регистра ввода.
Назначение:
Сделать текстовые сравнения стабильными.
Простыми словами:
Сначала переводим строку в единый регистр, потом сравниваем.
Для новичка:
Если это возможно, храните данные уже в нормализованном виде.
Аналогия:
Это как сравнивать слова в словаре после перевода в нижний регистр.
Пример:
SELECT u.id, u.emailFROM users uWHERE LOWER(u.email) = LOWER('VIP@MAIL.COM');🔎 Как это происходит на практике:
- Контекст: поиск пользователя по email.
- Действия: обе стороны сравнения приводят к
LOWER. - Результат: поиск находит запись независимо от регистра.
Характеристики:
- удобен для нормализации сравнения;
- может влиять на индексный план при функции над колонкой;
- если колонка уже хранится в нормализованном виде, нормализуйте входной параметр, а не колонку;
- для частых
LOWER(column)в PostgreSQL стоит делать функциональный индекс; - в PostgreSQL часто удобно использовать
ILIKEдля шаблонного case-insensitive поиска.
Пример production-подхода:
SELECT u.id, u.emailFROM users uWHERE u.email = LOWER(:email_param); CREATE INDEX idx_users_email_lower ON users (lower(email));Когда использовать:
Когда значения должны считаться одинаковыми независимо от регистра.
✅ Вывод:
LOWER/UPPER решают проблему регистра, но архитектурно лучше хранить нормализованные данные.📏 3. LENGTH: проверяем длину строки
LENGTH помогает валидировать качество данных:слишком короткие логины, пустые имена, подозрительно длинные поля.
🟢 Если совсем просто:
LENGTH возвращает количество символов в строке.🎯 Как понять, что этап прошёл успешно:
Вы можете найти и отфильтровать строки, не проходящие правила длины.
Назначение:
Контроль качества и базовая валидация текстовых полей.
Простыми словами:
Если длина не в допустимом диапазоне, запись нужно проверить.
Для новичка:
Перед
LENGTH часто полезно использовать TRIM, чтобы не считать лишние пробелы.
В PostgreSQL LENGTH(text) считает символы, а OCTET_LENGTH(text) — байты.Аналогия:
Это как проверка минимальной длины пароля при регистрации.
Пример:
SELECT u.id, u.username, LENGTH(TRIM(u.username)) AS username_lenFROM users uWHERE LENGTH(TRIM(u.username)) < 3;Бонус для PostgreSQL:
SELECT LENGTH('ёж') AS chars, OCTET_LENGTH('ёж') AS bytes;🔎 Как это происходит на практике:
- Контекст: аудит качества импортированных аккаунтов.
- Действия: проверяют длину
usernameпосле очистки. - Результат: быстро находят проблемные значения.
Характеристики:
- полезен для quality-check;
- часто комбинируется с
TRIM; - помогает строить правила валидации в SQL-слое.
Когда использовать:
Когда нужно контролировать минимальную/максимальную длину текстового поля.
✅ Вывод:
LENGTH — простой и эффективный инструмент контроля качества строковых данных.✂️ 4. TRIM: убираем лишние пробелы
Лишние пробелы в начале/конце строки — одна из самых частых причин «непонятных» несовпадений.
TRIM нормализует значения перед сравнением и показом.🟢 Если совсем просто:
TRIM удаляет пробелы по краям строки.🎯 Как понять, что этап прошёл успешно:
Значения, отличающиеся только пробелами, больше не считаются разными.
Назначение:
Очистить текст перед фильтрацией, сравнением и записью в отчёты.
Простыми словами:
Убираем «мусор» вокруг строки, чтобы сравнение было корректным.
Для новичка:
TRIM не удаляет пробелы в середине строки — только по краям.
TRIM(NULL) возвращает NULL; в WHERE это даёт UNKNOWN, поэтому NULL проверяйте отдельно.Аналогия:
Это как удалить случайные отступы вокруг слова в документе.
Пример:
SELECT u.id, TRIM(u.email) AS clean_emailFROM users uWHERE u.email IS NOT NULL AND TRIM(u.email) <> '';🔎 Как это происходит на практике:
- Контекст: импорт пользователей из CSV.
- Действия: очищают строки перед сравнением и сегментацией.
- Результат: исчезают ложные несовпадения.
Характеристики:
- снижает количество «грязных» строк;
- улучшает точность фильтров;
- часто применяется вместе с
LOWER.
Когда использовать:
Когда источник данных может содержать случайные пробелы.
✅ Вывод:
TRIM — базовая гигиена строк перед любой серьёзной фильтрацией.🔍 5. LIKE: поиск по шаблону
LIKE нужен, когда ищем строки не по точному совпадению, а по шаблону:префикс, суффикс или подстрока.
🟢 Если совсем просто:
LIKE проверяет, соответствует ли строка шаблону с % и _.🎯 Как понять, что этап прошёл успешно:
Вы выбираете правильный шаблон и понимаете, сколько строк он потенциально затронет.
Назначение:
Реализовать гибкий поиск по текстовым полям.
Простыми словами:
%abc%— содержитabc;abc%— начинается сabc;%abc— заканчивается наabc.
Для новичка:
В PostgreSQL
ILIKE даёт case-insensitive шаблонный поиск.
Если нужно искать буквальные символы % и _, используйте ESCAPE.Аналогия:
Это как поиск слова в тексте, где известна только часть фразы.
Пример:
SELECT u.id, u.emailFROM users uWHERE u.email ILIKE '%@gmail.com';Пример поиска спецсимвола:
SELECT c.id, c.commentFROM comments cWHERE c.comment LIKE '%\_%' ESCAPE '\';🔎 Как это происходит на практике:
- Контекст: сегмент пользователей по почтовому домену.
- Действия: фильтруют по шаблону домена.
- Результат: быстро получают нужный сегмент.
Характеристики:
- гибко для поиска по части строки;
LIKE 'abc%'часто может использовать btree-индекс;LIKE '%abc%'обычно не использует такой индекс и может требовать полный просмотр (нужны спец-индексы/FTS).
Когда использовать:
Когда нужен частичный поиск, а не точное сравнение.
✅ Вывод:
LIKE/ILIKE полезны для поиска, но шаблон нужно выбирать осознанно.⚡ 6. Комбинируем функции в реальных фильтрах
В production строковые функции редко используются по одной.
Чаще нужен конвейер: очистка (
Чаще нужен конвейер: очистка (
TRIM) -> нормализация (LOWER) -> проверка (LIKE/=).🟢 Если совсем просто:
Сначала приводим строку к единому виду, потом сравниваем.
🎯 Как понять, что этап прошёл успешно:
Фильтр стабильно работает на «грязных» данных из разных источников.
Назначение:
Сделать строковые фильтры устойчивыми к реальным данным.
Простыми словами:
Комбинация функций закрывает большинство проблем импорта и пользовательского ввода.
Для новичка:
Старайтесь не перегружать
WHERE огромными выражениями — выносите повторяемую логику в CTE.Аналогия:
Это как сначала помыть и отсортировать детали, а потом собирать механизм.
Пример:
WITH normalized_users AS ( SELECT u.id, TRIM(LOWER(u.email)) AS email_norm, TRIM(u.first_name) AS first_name_norm, TRIM(u.last_name) AS last_name_norm FROM users u)SELECT n.id, CONCAT(n.last_name_norm, ' ', n.first_name_norm) AS full_name, n.email_normFROM normalized_users nWHERE n.email_norm LIKE '%@company.com';Короткий
CASE для quality-check строк:SELECT u.id, u.email, CASE WHEN u.email IS NULL OR TRIM(u.email) = '' THEN 'missing' WHEN u.email NOT LIKE '%@%' THEN 'invalid' ELSE 'ok' END AS email_qualityFROM users u;🔎 Как это происходит на практике:
- Контекст: B2B-сегментация клиентов.
- Действия: нормализуют поля один раз в CTE и фильтруют по домену.
- Результат: меньше ошибок и более читаемый SQL.
Характеристики:
- повышает надёжность фильтрации;
- упрощает повторное использование логики;
- делает код легче для ревью.
Когда использовать:
Когда данные приходят из разных источников и требуют нормализации перед поиском.
✅ Вывод:
Комбинация
TRIM + LOWER + LIKE — базовый production-паттерн для строк.🧷 7. NULL-safe сравнение строк (PostgreSQL)
Иногда по бизнес-правилу нужно считать
Обычное
NULL равным NULL.Обычное
= так не работает, поэтому для PostgreSQL есть отдельные операторы.🟢 Если совсем просто:
IS NOT DISTINCT FROM — это = с учётом NULL,IS DISTINCT FROM — это <> с учётом NULL.🎯 Как понять, что этап прошёл успешно:
Вы можете явно управлять логикой сравнения, где
NULL — это тоже значение по правилу бизнеса.Назначение:
Делать предсказуемые сравнения строк в nullable-полях.
Простыми словами:
Если обе стороны
NULL, IS NOT DISTINCT FROM вернёт TRUE.Для новичка:
Это особенно полезно в фильтрах и
JOIN, где обычные сравнения с NULL дают UNKNOWN.Аналогия:
Это как сравнение с флажком «считать отсутствие значения осознанным совпадением».
Пример:
SELECT u.id, u.middle_nameFROM users uWHERE u.middle_name IS NOT DISTINCT FROM :middle_name_param;🔎 Как это происходит на практике:
- Контекст: фильтр по nullable-атрибуту в админке.
- Действия: используют
IS NOT DISTINCT FROMвместо=. - Результат: логика сравнения не ломается на
NULL.
Характеристики:
- убирает часть ошибок трёхзначной логики;
- делает условие явно читаемым для ревью;
- особенно полезен в PostgreSQL-проектах.
Когда использовать:
Когда бизнес-правило требует корректного сравнения с учётом
NULL.✅ Вывод:
IS [NOT] DISTINCT FROM — надёжный способ писать NULL-safe сравнения строк.🆚 Сравнение: базовые строковые функции
| Функция | Что делает | Когда полезна | Риск |
|---|---|---|---|
CONCAT | Склеивает строки | Формирование display-полей | Нечёткий формат без разделителей |
LOWER/UPPER | Нормализует регистр | Case-insensitive сравнения | Функция над колонкой может влиять на индекс |
LENGTH | Считает длину строки | Валидация качества | Неверная проверка без TRIM |
TRIM | Удаляет пробелы по краям | Очистка «грязных» данных | Не чистит пробелы внутри строки |
LIKE/ILIKE | Поиск по шаблону | Частичный текстовый поиск | %...% может быть дорогим |
✅ Вывод:
Выбор функции зависит от цели: представить, нормализовать, проверить или найти.
🧠 Must-Know (запомнить)
- Перед сравнением строк полезно делать
TRIMи нормализацию регистра. CONCATудобен для display-полей, но учитывайтеNULL: в PostgreSQL полезенCONCAT_WS, для кросс-СУБД —COALESCE.LENGTHлучше считать послеTRIM, если важна «чистая» длина.- В PostgreSQL
LENGTH— символы,OCTET_LENGTH— байты. LIKE 'abc%'иLIKE '%abc%'сильно отличаются по стоимости и по использованию индексов.- В PostgreSQL для case-insensitive поиска по шаблону есть
ILIKE. - Для буквального поиска
%и_вLIKEиспользуйтеESCAPE. - При работе со строками всегда учитывайте
NULL. - Для NULL-safe сравнения в PostgreSQL используйте
IS NOT DISTINCT FROM. - Сложную повторяемую нормализацию удобнее выносить в CTE.
✅ Вывод:
Эти правила закрывают основной набор практических задач по строкам в SQL.
❌ Частые мифы
❌ Миф:
LIKE всегда подходит для любого поиска.
✅ Как правильно: LIKE полезен для шаблонов, но точные сравнения лучше делать через =.
📎 Почему это важно: иначе запросы становятся тяжелее и менее предсказуемыми.❌ Миф:
TRIM и LOWER нужны только для «красоты».
✅ Как правильно: это базовая нормализация, влияющая на корректность фильтров.
📎 Почему это важно: без нормализации легко потерять нужные строки.❌ Миф:
CASE не нужен в теме строк.
✅ Как правильно: CASE часто помогает классифицировать строки по качеству/шаблону.
📎 Почему это важно: это мост к реальным аналитическим и API-задачам.❌ Миф: строковые функции почти не влияют на производительность.
✅ Как правильно: неудачное применение функций в фильтрах может сделать запрос заметно медленнее.
📎 Почему это важно: на больших таблицах это напрямую влияет на SLA.
🎤 Часто спрашивают на собеседованиях
❓ Вопрос: Когда использовать
CONCAT, а когда хранить поле отдельно?
✅ Ответ: CONCAT используйте для вычисляемого представления, исходные компоненты храните отдельно.❓ Вопрос: Зачем делать
LOWER() при сравнении email?
✅ Ответ: чтобы убрать зависимость от регистра и не терять логически одинаковые значения.❓ Вопрос: Чем
LENGTH полезен в реальной задаче?
✅ Ответ: помогает валидировать качество данных и находить подозрительно короткие/длинные значения.❓ Вопрос: Что делает
TRIM и чего он не делает?
✅ Ответ: удаляет пробелы по краям строки, но не убирает пробелы внутри строки.❓ Вопрос: В чём разница между
LIKE и ILIKE в PostgreSQL?
✅ Ответ: LIKE чувствителен к регистру, ILIKE — нет.❓ Вопрос: Почему
LIKE '%abc%' может быть медленным?
✅ Ответ: такой шаблон часто требует просмотра большого числа строк, особенно без подходящих индексов.🚨 Типичные ошибки
- Сравнивать «грязные» строки без
TRIM. - Путать точное сравнение (
=) и поиск по шаблону (LIKE). - Применять
LOWER()только к одной стороне сравнения. - Забывать про
NULLв строковых фильтрах. - Использовать
%...%без оценки стоимости на больших таблицах. - Считать длину строки без предварительной очистки пробелов.
- Дублировать одну и ту же строковую нормализацию в нескольких местах запроса.
✅ Вывод:
Большинство проблем со строками решается дисциплиной нормализации и осознанным выбором фильтра.
✅ Best Practices
- Нормализуйте строку перед сравнением:
TRIM+LOWER. - Отделяйте display-логику (
CONCAT) от логики фильтрации. - Используйте
LENGTHдля quality-check и валидации. - Выбирайте шаблон
LIKEосознанно и минимально необходимый. - Для повторяемой нормализации выносите логику в CTE.
- Проверяйте граничные кейсы: пустая строка, пробелы,
NULL. - Для тяжёлых строковых фильтров смотрите
EXPLAIN.
✅ Вывод:
Хорошая работа со строками в SQL — это баланс корректности, читаемости и производительности.
🏁 Заключение
Строковые функции — фундамент повседневной SQL-практики:
они нужны и в фильтрации, и в подготовке данных для API, и в аналитике.
они нужны и в фильтрации, и в подготовке данных для API, и в аналитике.
Если вы уверенно используете
то контролируете и качество данных, и предсказуемость результата.
CONCAT, LOWER/UPPER, LENGTH, TRIM, LIKE,то контролируете и качество данных, и предсказуемость результата.
✅ Вывод:
Сильный инженер по SQL умеет не только считать, но и грамотно «готовить» текстовые данные.