sqlpostgresqlinitcapstring-functions

INITCAP en PostgreSQL: poner en mayuscula cada palabra

Como INITCAP pone en mayuscula la primera letra de cada palabra en PostgreSQL, donde falla con apostrofos y guiones, y como imitarlo en MySQL.

3 min de lecturaReferencesql · postgresql · initcap · string-functions · mysql
Este artículo está actualmente en ruso — la traducción está en curso.

INITCAP — это функция PostgreSQL, которая делает первую букву каждого слова заглавной, а остальные буквы строчными. Это удобно для приведения имён, городов и названий к единому виду title-case, но у функции есть неочевидные ограничения, о которых стоит знать заранее.

INITCAP решает узкую задачу — презентационную нормализацию регистра, а не каноничное написание имени. Поэтому в запросе важно видеть, что initcap(name) отдаёт значение «для показа», а не утверждает юридически точную форму фамилии. Если вы прогоняете данные через эту функцию в отчётах, проверках качества или разовых чистках, вынесите её в явный слой — view, generated column или staging-преобразование, — где правило приведения регистра можно протестировать на пограничных именах и переиспользовать, а не дублировать в каждом запросе.

Что делает INITCAP

INITCAP берёт строку, разбивает её на слова и для каждого слова поднимает первую букву в верхний регистр, а все остальные опускает в нижний. Словом считается любая последовательность буквенно-цифровых символов, а границей слова — любой небуквенно-цифровой символ (пробел, точка, дефис, апостроф и так далее).

SELECT initcap('john DOE');        -- John Doe
SELECT initcap('HELLO world');     -- Hello World
SELECT initcap('order #42 paid');  -- Order #42 Paid

Обратите внимание: исходный регистр середины слова не сохраняется. INITCAP всегда нормализует слово к виду «первая заглавная, дальше строчные», поэтому 'McDONALD' превратится в 'Mcdonald'.

Нормализация имён и городов

Типичный сценарий — данные пришли из формы или импорта в произвольном регистре, и их нужно показать аккуратно.

-- Tidy up names captured in mixed or all-caps form
SELECT id, initcap(name) AS display_name
FROM users
ORDER BY display_name;

-- Normalize country labels for a report
SELECT initcap(country) AS country, count(*) AS users
FROM users
GROUP BY initcap(country)
ORDER BY users DESC;

Можно сразу записать приведённое значение обратно, если хотите хранить данные в чистом виде:

-- One-off cleanup of inconsistent name casing
UPDATE users
SET name = initcap(name)
WHERE name <> initcap(name);

Где INITCAP ломается

Главная ловушка в том, что INITCAP считает границей слова любой неалфавитно-цифровой символ. Для реальных имён это часто неверно:

  • апостроф начинает «новое слово», поэтому буква после него становится заглавной;
  • дефисы и апострофы внутри имени тоже считаются границами слов;
  • внутренние заглавные (как в McDonald) теряются — они опускаются в нижний регистр.
SELECT initcap('o''brien');        -- O'Brien   (B capitalized after the quote)
SELECT initcap('jean-luc PICARD'); -- Jean-Luc Picard
SELECT initcap('mcdonald');        -- Mcdonald  (not McDonald)

Ловушка: INITCAP не знает культурных правил вроде McDonald, van der Berg или DeShawn. После апострофа и дефиса он всегда ставит заглавную, а средние заглавные внутри слова теряет. Для брендов и фамилий с особым написанием полагаться на INITCAP нельзя — храните каноничную форму отдельно.

Второй нюанс — локаль и Unicode. PostgreSQL приводит регистр согласно локали базы данных, и для некоторых пар (например, турецкая i/I) результат может отличаться от ожидаемого. Для ASCII-имён всё предсказуемо, но для многоязычных данных проверяйте поведение в своей локали.

Если INITCAP портит только отдельные записи, не пишите его результат на всю таблицу — отберите проблемные строки и поправьте их точечно:

-- Apply initcap only where it is safe: skip names with apostrophes or hyphens
UPDATE users
SET name = initcap(name)
WHERE name <> initcap(name)
  AND name !~ '[''-]';

INITCAP в MySQL: эмуляция

В MySQL функции INITCAP нет. Для одного слова её легко собрать из UPPER, LOWER и SUBSTRING:

-- MySQL: capitalize only the first letter of a single word
SELECT CONCAT(
  UPPER(SUBSTRING(name, 1, 1)),
  LOWER(SUBSTRING(name, 2))
) AS display_name
FROM users;

Для многословных строк такого выражения мало — нужно обработать каждое слово. На практике это решают пользовательской функцией, которая идёт по строке и поднимает букву после каждого пробела, либо выносят логику в приложение. ClickHouse тоже не имеет INITCAP, но предлагает upperUTF8, lowerUTF8 и substring, из которых строится та же эмуляция.

Перед переносом этой эмуляции между PostgreSQL, MySQL и ClickHouse прогоните initcap на маленькой таблице с теми самыми крайними значениями, на которых функция и спотыкается: имена с апострофом (o'brien) и дефисом (jean-luc), внутренние заглавные (mcdonald), пустая строка, NULL и не-ASCII буквы. На «чистых» односложных именах все три движка совпадут, а расходятся они ровно на этих пограничных строках — поэтому держите их в тесте именно для приведения регистра, а не только для итогового отчёта.

И последнее про производительность: initcap(name) в условии WHERE или GROUP BY оборачивает колонку функцией, а значит обычный индекс по name не используется и каждая строка нормализуется заново. На большой таблице это заметно: либо храните уже приведённое значение в отдельной колонке (например, generated column) и индексируйте её, либо чистите регистр разово через UPDATE, как показано выше, а не на каждом чтении.

Короткий итог: в PostgreSQL INITCAP отлично подходит для быстрой нормализации простых ASCII-строк, но не доверяйте ему фамилии с апострофами, дефисами и внутренними заглавными; в MySQL и ClickHouse того же эффекта добиваются вручную через UPPER + LOWER + SUBSTRING.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador