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;
Самые ходовые коды:
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, DAY — MONDAY, day — monday. Текстовые поля дополняются пробелами до фиксированной ширины, и чтобы их убрать, добавляют модификатор 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;
Язык названий берётся из параметра lc_time. Чтобы получить русские месяцы в одном запросе, переопределите его прямо в вызове:
SELECT TO_CHAR(
created_at,
'TMDay, DD TMMonth YYYY'
) AS ru_date
FROM orders;
Префикс 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;
Как читать маску:
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 с совершенно другими кодами на основе %.
SELECT TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI') FROM users;
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 непереносим, при смене СУБД его придётся переписывать целиком.
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,DAY—MONDAY,day—monday. Текстовые поля дополняются пробелами до фиксированной ширины, и чтобы их убрать, добавляют модификатор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;Ключевые расхождения:
MI, MySQL —%i(а%mв MySQL это месяц).HH24, MySQL —%H.Month/Mon, MySQL —%M/%b.FORMAT(amount, 2), а неTO_CHAR.Ещё одно отличие важно для NULL:
TO_CHARнаNULL-значении возвращаетNULL, а не строку, поэтому в отчёте вместо отформатированной даты появится пустая ячейка — оберните вызов вCOALESCE, если нужен текст-заглушка. Месяцы01-12в кодеMMPostgreSQL никогда не путает с минутами, а вот%mMySQL и%MClickHouse означают разное, так что один и тот же шаблон даст три разных результата.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непереносим, при смене СУБД его придётся переписывать целиком.