sqlpostgresqlto-charformatting

SQL TO_CHAR: Formatting Dates and Numbers into Strings with Templates

How TO_CHAR turns a date or number into a string with a template: YYYY-MM-DD, HH24:MI, month names, thousands separators, and how it differs from MySQL.

3 Min. LesezeitReferencesql · postgresql · to-char · formatting · dates · mysql
Dieser Artikel ist derzeit auf Russisch — die englische Übersetzung ist in Arbeit.

TO_CHAR берёт дату, метку времени или число и возвращает строку, собранную по текстовому шаблону. Это главный инструмент PostgreSQL для «человеческого» вывода: фиксированный формат даты для отчёта, время без секунд, цена с разделителями разрядов.

Сигнатура простая: первым аргументом идёт значение типа date, timestamp, interval или числовое, вторым — строка-шаблон, а на выходе всегда text. Поэтому TO_CHAR имеет смысл ставить в самом конце запроса, когда значение уже посчитано и осталось его только показать. Если же отформатированную строку потом сортировать, фильтровать или соединять по ней, порядок поедет по алфавиту, а не по дате: '17.06.2026' встанет раньше '05.07.2026'. Поэтому в WHERE, ORDER BY и JOIN держите исходный timestamp, а TO_CHAR применяйте только к колонкам итоговой выборки.

Шаблоны для дат и времени

Первый аргумент — значение, второй — строка-шаблон из кодов поля. Регистр и пунктуация в шаблоне сохраняются как есть, поэтому дефисы, двоеточия и пробелы вы расставляете сами.

SELECT TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI') AS ts
FROM users
ORDER BY created_at
LIMIT 5;
-- 2026-06-17 14:32

Самые ходовые коды:

  • YYYY — год из 4 цифр, YY — из двух.
  • MM — месяц числом (01-12), DD — день, HH24 — час в 24-часовом формате, MI — минуты, SS — секунды.
  • HH12 с AM/PM — для 12-часового времени.
  • Day, Mon, Month — текстовые названия дня и месяца (об их регистре ниже).

Внимание на ловушку: MM — это месяц, а MI — минуты. Очень легко написать HH24:MM и получить вместо минут номер месяца — ошибка тихая, синтаксически запрос валиден, и в отчёте просто появятся неправильные значения. Если время нужно без секунд, останавливайтесь на MI и не добавляйте SS.

Названия месяцев и локализация

Коды Day, Month, Mon, Dy повторяют регистр шаблона: Day даёт Monday, DAYMONDAY, daymonday. Текстовые поля дополняются пробелами до фиксированной ширины, и чтобы их убрать, добавляют модификатор FM.

SELECT
  TO_CHAR(created_at, 'FMDay, FMDD FMMonth YYYY') AS human_date,
  TO_CHAR(created_at, 'Dy')                       AS short_dow
FROM orders
LIMIT 3;
-- Wednesday, 17 June 2026 | Wed

Язык названий берётся из параметра lc_time. Чтобы получить русские месяцы в одном запросе, переопределите его прямо в вызове:

SELECT TO_CHAR(
  created_at,
  'TMDay, DD TMMonth YYYY'
) AS ru_date
FROM orders;
-- with lc_time = ru_RU.UTF-8: subbota, 17 ijunja 2026

Префикс TM (translate mode) включает локализованные названия. Без него вы всегда получите английские Monday/June.

Форматирование чисел

С числами TO_CHAR управляет шириной, разрядами и знаком через маску из символов 9, 0, D, G и других.

SELECT
  TO_CHAR(amount, 'FM999G999G990D00')  AS money,
  TO_CHAR(id,     '0000')              AS padded_id
FROM orders
LIMIT 3;
-- 1,250.00 | 0042

Как читать маску:

  • 9 — цифра, но ведущие нули скрываются; 0 — цифра с гарантированным нулём (для дополнения слева).
  • D — десятичный разделитель, G — разделитель групп; конкретные символы зависят от локали (lc_numeric).
  • FM снова срезает ведущие пробелы, которые TO_CHAR резервирует под знак.
  • L подставит символ валюты, S — знак +/-.

Ловушка: без FM положительные числа выводятся с ведущим пробелом на месте минуса, и строки в отчёте «съезжают» по ширине. Для денег почти всегда нужен FM и явный 0 перед D, иначе дробь без целой части вроде 0.50 превратится в .50. Если значение шире маски, PostgreSQL вернёт строку из символов # — это сигнал, что цифр в шаблоне не хватает и его надо расширить.

Чем PostgreSQL отличается от MySQL

В MySQL функции TO_CHAR для дат нет — там используют DATE_FORMAT с совершенно другими кодами на основе %.

-- PostgreSQL
SELECT TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI') FROM users;

-- MySQL
SELECT DATE_FORMAT(created_at, '%Y-%m-%d %H:%i') FROM users;

Ключевые расхождения:

  • Минуты: PostgreSQL — MI, MySQL — %i%m в MySQL это месяц).
  • Час 24: PostgreSQL — HH24, MySQL — %H.
  • Название месяца: PostgreSQL — Month/Mon, MySQL — %M/%b.
  • Числа: в MySQL для разрядов берут отдельную FORMAT(amount, 2), а не TO_CHAR.

Ещё одно отличие важно для NULL: TO_CHAR на NULL-значении возвращает NULL, а не строку, поэтому в отчёте вместо отформатированной даты появится пустая ячейка — оберните вызов в COALESCE, если нужен текст-заглушка. Месяцы 01-12 в коде MM PostgreSQL никогда не путает с минутами, а вот %m MySQL и %M ClickHouse означают разное, так что один и тот же шаблон даст три разных результата.

TO_CHAR над колонкой в WHERE или ORDER BY мешает планировщику использовать обычный B-tree индекс по created_at: PostgreSQL придётся пересчитывать строку для каждой строки таблицы. Если фильтровать всё же нужно по форматированному виду (например, по 'YYYY-MM'), заведите выражательный индекс CREATE INDEX ON orders (TO_CHAR(created_at, 'YYYY-MM')) — тогда то же выражение в запросе попадёт под индекс. Для обычных же отчётов оставляйте TO_CHAR в списке SELECT, и план остаётся таким же дешёвым, как без форматирования.

ClickHouse идёт третьим путём: formatDateTime(created_at, '%Y-%m-%d %H:%M'), коды как у MySQL, но %M здесь минуты, а не месяц. Вывод один: шаблон TO_CHAR непереносим, при смене СУБД его придётся переписывать целиком.

Übe an echten Aufgaben

Löse Aufgaben im SQL-Trainer mit sofortiger Bewertung und Hinweisen.

Trainer öffnen