Справочник

Справочник по SQL

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

Основы: SELECT и фильтрация4

SELECT … FROM
Статья
SELECT col1, col2 FROM table_name;

Выбрать колонки из таблицы.

См. также:····
SELECT * FROM t
WHERE col = 5 AND status = 'active';

Фильтр строк по условию: =, <>, <, >, AND, OR, IN, BETWEEN, LIKE.

См. также:····
ORDER BY
Статья
SELECT * FROM t ORDER BY created_at DESC;

Сортировка результата. ASC по возрастанию (по умолчанию), DESC по убыванию.

См. также:····
SELECT * FROM t ORDER BY id LIMIT 10;

Ограничить количество строк в результате.

См. также:····

Соединения таблиц (JOIN)7

INNER JOIN
Статья
SELECT * FROM a JOIN b ON a.id = b.a_id;

Только те строки, для которых есть пара в обеих таблицах.

См. также:·····
LEFT JOIN
Статья
SELECT * FROM a LEFT JOIN b ON a.id = b.a_id;

Все строки из левой таблицы; справа — NULL, если пары нет.

См. также:·····
Алиасы
Статья
SELECT u.name
FROM users u
JOIN orders o ON o.user_id = u.id;

Короткие имена для таблиц — обязательны при одинаковых колонках.

См. также:····
CROSS JOIN
Статья
SELECT * FROM sizes CROSS JOIN colors;

Декартово произведение — каждая строка слева с каждой справа. Для генерации всех комбинаций.

См. также:·····
FULL OUTER JOIN
Статья
SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id;

Все строки обеих таблиц; NULL там, где пары нет. В MySQL нет FULL JOIN — эмулируют через LEFT ∪ RIGHT.

См. также:·····
Self-join (таблица сама с собой)
Статья
SELECT e.name, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

Таблица соединяется сама с собой через алиасы — для иерархий «сотрудник → руководитель».

См. также:·····
Анти-join (LEFT JOIN + IS NULL)
Статья
SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;

Строки слева, у которых СПРАВА нет пары — «пользователи без заказов». Альтернатива NOT EXISTS.

См. также:·····

Агрегация и группировка30

SELECT COUNT(*), COUNT(email) FROM users;

Считает строки в группе. COUNT(*) — все строки, COUNT(col) — только не-NULL, COUNT(DISTINCT col) — уникальные значения.

См. также:···
SELECT SUM(amount) FROM orders;

Сумма числовых значений в группе. NULL игнорируются. Для пустой группы возвращает NULL (не 0).

SELECT AVG(price) FROM products;

Среднее арифметическое. NULL не входят в делитель. Целочисленную колонку приводите к numeric, иначе дробная часть отбросится.

SELECT MIN(created_at) FROM orders;

Минимальное значение в группе. Работает с числами, датами и строками. NULL игнорируются.

SELECT MAX(created_at) FROM orders;

Максимальное значение в группе. Работает с числами, датами и строками. NULL игнорируются.

GROUP BY
Статья
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id;

Сгруппировать строки. Все колонки SELECT, кроме агрегатов, должны быть в GROUP BY.

См. также:···
SELECT user_id, COUNT(*)
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 5;

WHERE для агрегатов — фильтр уже сгруппированных данных.

См. также:···
DISTINCT
Статья
SELECT DISTINCT country FROM users;

Убрать дубликаты строк в результате.

См. также:····
COUNT(*) FILTERPostgreSQL
Статья
COUNT(*) FILTER (WHERE type = 'view') AS views

Условный агрегат: считаем COUNT/SUM только по строкам, где WHERE-условие верно. Заменяет три отдельных запроса на один.

См. также:·····
STRING_AGGPostgreSQL
Статья
STRING_AGG(name, ', ' ORDER BY created_at)

Склеить значения в строку с разделителем. ORDER BY внутри гарантирует порядок.

См. также:··
ARRAY_AGGPostgreSQL
Статья
ARRAY_AGG(amount ORDER BY created_at)

Собрать значения в массив. Удобно для аудита истории на одну строку отчёта.

См. также:··
GROUPING SETSPostgreSQL
Статья
GROUP BY GROUPING SETS ((kind), (user_id), ())

Несколько уровней группировки в одном запросе — построчно: по kind, по user_id и общий итог.

См. также:···
ROLLUPPostgreSQL
Статья
GROUP BY ROLLUP (DATE_TRUNC('month', ts))

Тот же группировочный уровень + строка с общим итогом (одна NULL-строка в конце).

См. также:···
PERCENTILE_CONTPostgreSQL
Статья
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount)

Медиана / квантили. Устойчивее к выбросам, чем AVG.

См. также:···
UNNESTPostgreSQL
Статья
SELECT tag FROM articles, UNNEST(tags) tag

Развернуть массив в строки — одна строка на каждый элемент массива.

См. также:··
COUNT(DISTINCT)
Статья
SELECT COUNT(DISTINCT user_id) FROM events;

Считает число уникальных значений. На больших таблицах дорог — для приблизительной оценки используйте APPROX_COUNT_DISTINCT / HLL.

См. также:···
BOOL_AND / BOOL_OR
Статья
SELECT BOOL_AND(active), BOOL_OR(is_admin) FROM users;

Агрегаты по булевым: BOOL_AND — true, если ВСЕ строки true; BOOL_OR — если хотя бы одна. NULL игнорируются.

См. также:·
SELECT dept, EVERY(salary > 0) FROM emp GROUP BY dept;

Стандартный SQL-синоним BOOL_AND — true, если условие выполнено во всех строках группы.

См. также:·
Стандартное отклонение
Статья
SELECT STDDEV_SAMP(amount), STDDEV_POP(amount) FROM orders;

Стандартное отклонение: _SAMP по выборке (делитель n-1), _POP по всей совокупности (делитель n). Голый STDDEV = STDDEV_SAMP.

См. также:···
Дисперсия
Статья
SELECT VAR_SAMP(amount), VAR_POP(amount) FROM orders;

Дисперсия — квадрат стандартного отклонения. _SAMP по выборке, _POP по совокупности. Голый VARIANCE = VAR_SAMP.

См. также:···
Мода (самое частое значение)PostgreSQL
Статья
SELECT MODE() WITHIN GROUP (ORDER BY status) FROM tickets;

Мода — самое частое значение в группе. При равной частоте берётся первое по ORDER BY.

См. также:···
PERCENTILE_DISC
Статья
SELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY amount) FROM orders;

Дискретный перцентиль — возвращает реально существующее значение из данных, в отличие от PERCENTILE_CONT, который интерполирует.

См. также:···
BIT_AND / BIT_OR
Статья
SELECT BIT_OR(flags), BIT_AND(flags) FROM permissions;

Побитовые агрегаты по integer-колонке: BIT_OR собирает все выставленные биты, BIT_AND — общие для всех строк. Для флаговых масок.

См. также:·
JSON_AGG / JSONB_AGGPostgreSQL
Статья
SELECT JSONB_AGG(t ORDER BY t.id) FROM tasks t;

Собрать строки в JSON-массив — удобно отдавать вложенные данные одним запросом. JSONB_AGG хранит как jsonb (быстрее, без дублей ключей).

См. также:····
JSONB_OBJECT_AGGPostgreSQL
Статья
SELECT JSONB_OBJECT_AGG(key, value) FROM settings;

Свернуть пары ключ-значение в один JSON-объект. Идеально для превращения таблицы настроек в карту.

См. также:····
Корреляция
Статья
SELECT CORR(price, sales) FROM products;

Коэффициент корреляции Пирсона между двумя колонками: от -1 до 1. Мера линейной связи.

См. также:···
Линейная регрессия
Статья
SELECT REGR_SLOPE(y, x), REGR_INTERCEPT(y, x) FROM points;

Наклон и пересечение линии линейной регрессии y по x — тренд одним агрегатом, без внешней матстатистики.

См. также:···
SELECT REGR_R2(y, x) FROM points;

Коэффициент детерминации R² регрессии y по x: 0..1, насколько хорошо линия описывает данные.

См. также:···
Пивот через FILTERPostgreSQL
Статья
SELECT user_id,
  MAX(amount) FILTER (WHERE kind = 'deposit')  AS deposit,
  MAX(amount) FILTER (WHERE kind = 'withdraw') AS withdraw
FROM tx GROUP BY user_id;

Разложить строки в колонки (pivot): MAX/SUM с FILTER по каждой категории. Заменяет ручной набор CASE-агрегатов.

См. также:···
SELECT region, product, SUM(amount)
FROM sales
GROUP BY CUBE (region, product);

Все возможные комбинации группировок сразу: по region, по product, по обоим и общий итог. Для кросс-табличных отчётов.

См. также:···

Подзапросы3

IN (подзапрос)
Статья
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);

Проверить, что значение есть в списке, который сам собирается запросом.

См. также:···
SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

Вернуть строку, только если внутренний запрос нашёл хотя бы одну строку.

См. также:···
Скалярный подзапрос
Статья
SELECT
  name,
  (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS cnt
FROM users u;

Подзапрос, возвращающий одно значение — можно поставить в SELECT.

См. также:···

Множества (UNION/INTERSECT)3

UNION / UNION ALL
Статья
SELECT id FROM a
UNION ALL
SELECT id FROM b;

Склеить результаты двух запросов (одинаковое число колонок и совместимые типы). UNION убирает дубликаты, UNION ALL оставляет (и быстрее).

См. также:·
INTERSECT
Статья
SELECT user_id FROM purchases
INTERSECT
SELECT user_id FROM refunds;

Строки, которые есть в ОБОИХ запросах. В MySQL — начиная с 8.0.31.

См. также:·
EXCEPT (MINUS)
Статья
SELECT user_id FROM users
EXCEPT
SELECT user_id FROM banned;

Строки из первого запроса, которых НЕТ во втором. В Oracle это MINUS.

См. также:·

Оконные функции9

ROW_NUMBER
Статья
SELECT
  name,
  ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
FROM players;

Уникальный номер для каждой строки внутри окна.

См. также:····
RANK / DENSE_RANK
Статья
RANK() OVER (PARTITION BY dept ORDER BY salary DESC)

Ранг с пропусками (RANK) или без пропусков (DENSE_RANK) при равных значениях.

См. также:··
PARTITION BY
Статья
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at)

Разбить окно по группам — агрегат считается в пределах группы.

См. также:·
LAG / LEAD
Статья
LAG(price, 1) OVER (ORDER BY date)

Значение из предыдущей (LAG) или следующей (LEAD) строки окна.

См. также:··
NTILE(4) OVER (ORDER BY score DESC)

Разбить строки на N равных групп по порядку. На неровных N группы получаются 3-3-2-2 (лишние идут в первые).

См. также:·····
FIRST_VALUE / LAST_VALUE
Статья
LAST_VALUE(score) OVER (
  PARTITION BY team_id ORDER BY score DESC
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)

Первое / последнее значение в окне. Для LAST_VALUE обязательно расширить фрейм через ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING — иначе фрейм по умолчанию обрежет правый край.

См. также:··
PERCENT_RANK
Статья
PERCENT_RANK() OVER (ORDER BY score DESC, player_id)

Процентильный ранг 0..1. Tiebreaker во второй колонке делает результат детерминированным при равных score.

См. также:··
NTH_VALUE
Статья
NTH_VALUE(amount, 2) OVER (
  PARTITION BY customer_id ORDER BY amount DESC
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)

N-ое значение из окна. Тоже требует расширенного фрейма.

См. также:··
Оконный фрейм
Статья
AVG(x) OVER (ORDER BY d ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)

Скользящие окна: «последние 7 дней включительно», «3-дневное среднее» и т.п.

См. также:····

CTE и рекурсия (WITH)5

WITH … AS
Статья
WITH active AS (
  SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM active WHERE country = 'RU';

Именованный временный результат — удобно разбивать сложный запрос на шаги.

См. также:··
Несколько CTE
Статья
WITH a AS (...), b AS (...)
SELECT * FROM a JOIN b ON ...;

Несколько CTE через запятую, читаются сверху вниз.

См. также:··
WITH RECURSIVE
Статья
WITH RECURSIVE chain AS (
  SELECT id, manager_id FROM emp WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.manager_id FROM emp e JOIN chain c ON e.manager_id = c.id
)
SELECT * FROM chain;

Обход иерархии: стартовый запрос → UNION ALL → шаг рекурсии. Идеально для оргсхем, графов, цепочек.

См. также:··
FROM customers c
LEFT JOIN LATERAL (
  SELECT * FROM orders WHERE customer_id = c.id
  ORDER BY amount DESC LIMIT 2
) l ON true

Подзапрос видит колонки внешнего запроса. Удобно для «top-N на каждого X». LEFT JOIN LATERAL … ON true сохраняет внешние строки без заказов; форма через запятую их молча отбрасывает.

См. также:·····
generate_seriesPostgreSQL
Статья
generate_series('2024-01-01'::date, '2024-01-15'::date, '1 day')

Сгенерировать календарь / шкалу. Включает обе границы. Базовый трюк для заполнения пропусков нулями.

См. также:··

Изменение данных (DML)10

INSERT INTO t (col1, col2) VALUES (1, 'a'), (2, 'b');

Добавить строки в таблицу.

См. также:··
UPDATE t SET col = 'x' WHERE id = 5;

Изменить существующие строки. WHERE обязателен — без него обновятся ВСЕ строки.

См. также:··
DELETE FROM t WHERE id = 5;

Удалить строки. WHERE обязателен.

См. также:··
ON CONFLICT DO NOTHINGPostgreSQL
Статья
INSERT INTO t (id) VALUES (1)
ON CONFLICT (id) DO NOTHING;

Идемпотентная вставка — повторный запуск молча пропускает уже существующие строки.

См. также:··
UPSERTPostgreSQL
Статья
INSERT INTO t (id, n) VALUES (1, 1)
ON CONFLICT (id) DO UPDATE
  SET n = t.n + EXCLUDED.n;

UPSERT: вставить или обновить. EXCLUDED.col — то значение, которое мы пытались вставить.

См. также:··
MERGEPostgreSQL
Статья
MERGE INTO t USING src ON t.id = src.id
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ...;

Postgres 15+ — UPSERT-альтернатива с ветками MATCHED / NOT MATCHED и условиями в каждой.

См. также:··
RETURNINGPostgreSQL
Статья
INSERT INTO t (name) VALUES ('a')
RETURNING id, name;

Вернуть только что вставленные / обновлённые / удалённые строки сразу после операции — без второго round-trip.

См. также:·····
DELETE … USINGPostgreSQL
Статья
DELETE FROM orders o
USING customers c
WHERE o.customer_id = c.id AND c.country = 'US';

JOIN-стилевой DELETE — фильтр по другой таблице без подзапроса.

См. также:··
UPDATE … FROMPostgreSQL
Статья
UPDATE customers c SET total = s.total
FROM (SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY customer_id) s
WHERE c.id = s.customer_id;

Массовый UPDATE с агрегатом из подзапроса.

См. также:··
CTE + DELETE … RETURNINGPostgreSQL
Статья
WITH moved AS (
  DELETE FROM orders WHERE old RETURNING *
)
INSERT INTO archive SELECT * FROM moved;

Атомарная архивация: переносим строки одним statement-ом, без race-окна между DELETE и INSERT.

См. также:··

Схема (DDL)10

CREATE TABLE
Статья
CREATE TABLE t (
  id INTEGER PRIMARY KEY,
  name VARCHAR(100) NOT NULL
);

Создать таблицу с типизированными колонками.

См. также:···
ALTER TABLE
Статья
ALTER TABLE t ADD COLUMN created_at TIMESTAMP;

Изменить структуру существующей таблицы.

См. также:···
ALTER TABLE products
ADD CONSTRAINT price_positive CHECK (price > 0);

Запрет на невалидные значения уровнем БД, а не кодом.

См. также:···
FK ON DELETE
Статья
FOREIGN KEY (post_id) REFERENCES posts(id)
  ON DELETE CASCADE   -- or SET NULL / RESTRICT

Что делать с дочерней строкой, когда удаляют родителя: каскад, обнулить FK или запретить.

См. также:···
NOT VALID + VALIDATEPostgreSQL
Статья
ALTER TABLE t
  ADD CONSTRAINT fk REFERENCES p(id) NOT VALID;
ALTER TABLE t VALIDATE CONSTRAINT fk;

Добавить FK на большой проде без жёсткого lock-а: NOT VALID — моментально, VALIDATE — без блокировки писателей.

См. также:···
Computed-колонка
Статья
total NUMERIC GENERATED ALWAYS AS (price * (1 + tax)) STORED

Значение колонки вычисляется автоматически по формуле — формула живёт в одном месте.

См. также:···
Partial UNIQUEPostgreSQL
Статья
CREATE UNIQUE INDEX u ON users (email)
WHERE deleted_at IS NULL;

Уникальность только по живым строкам — для soft-delete, чтобы повторно регистрироваться можно было после удаления.

См. также:···
Range-партиционированиеPostgreSQL
Статья
CREATE TABLE logs (...) PARTITION BY RANGE (ts);
CREATE TABLE logs_2024 PARTITION OF logs
  FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');

Разрезать большую таблицу по диапазону. Старые партиции дропаются за миллисекунды.

См. также:···
TRIGGERPostgreSQL
Статья
CREATE TRIGGER touch BEFORE UPDATE ON notes
FOR EACH ROW EXECUTE FUNCTION touch_updated_at();

Авто-логика на уровне БД — например, обновлять updated_at без участия кода.

См. также:···
MATERIALIZED VIEWPostgreSQL
Статья
CREATE MATERIALIZED VIEW v AS SELECT ...;
REFRESH MATERIALIZED VIEW v;

Закэшированный результат тяжёлого запроса. Обновляется по расписанию через REFRESH.

См. также:···

Строки и даты9

LOWER / UPPER / LENGTH
Статья
LOWER(name), UPPER(code), LENGTH(text)

Нижний/верхний регистр, длина строки.

CONCAT(first_name, ' ', last_name)

Склеить строки. В PostgreSQL также работает оператор || (в MySQL по умолчанию || — это логическое ИЛИ, не конкатенация).

См. также:··
EXTRACT(YEAR FROM created_at), EXTRACT(MONTH FROM created_at)

Достать часть даты — год, месяц, день.

См. также:···
DATE_TRUNCPostgreSQL
Статья
DATE_TRUNC('month', created_at)

Округлить дату/время вниз до периода (день/неделя/месяц). Базовый инструмент для группировки по времени.

См. также:···
NOW / CURRENT_DATE + INTERVAL
Статья
WHERE created_at >= NOW() - INTERVAL '7 days'

Текущий момент (NOW()) / сегодня (CURRENT_DATE) и арифметика с интервалами — «за последние 7 дней».

См. также:·
CAST / приведение типа
Статья
CAST(price AS INTEGER)   -- or price::int

Привести значение к другому типу. CAST(x AS type) — стандарт; x::type — короткая запись PostgreSQL.

См. также:·
TRIM / SUBSTRING / REPLACE
Статья
TRIM(name), SUBSTRING(code FROM 1 FOR 3), REPLACE(phone, '-', '')

Обрезать пробелы, вырезать подстроку, заменить фрагмент. Базовая чистка строк.

См. также:·
SPLIT_PARTPostgreSQL
Статья
SPLIT_PART(email, '@', 2)   -- domain from an e-mail

Разбить строку по разделителю и взять N-ю часть. В MySQL — SUBSTRING_INDEX.

См. также:···
ILIKEPostgreSQL
Статья
WHERE name ILIKE '%ivan%'

Регистронезависимый LIKE (PostgreSQL). В MySQL обычный LIKE и так нечувствителен к регистру при стандартной коллации.

См. также:···

Строковые функции16

LEFT / RIGHT
Статья
LEFT(code, 3), RIGHT(phone, 4)

Взять N символов с начала (LEFT) или с конца (RIGHT) строки.

См. также:···
Поиск подстрокиPostgreSQL
Статья
POSITION('@' IN email), STRPOS(email, '@')

Позиция первого вхождения подстроки (с 1), 0 если не найдено. STRPOS — короткая форма PostgreSQL.

См. также:···
LPAD / RPAD
Статья
LPAD(id::text, 6, '0'), RPAD(name, 20, ' ')

Дополнить строку до нужной длины слева (LPAD) или справа (RPAD). Классика — нули перед номером.

См. также:···
INITCAPPostgreSQL
Статья
INITCAP('john DOE')   -- John Doe

Первая буква каждого слова в верхний регистр, остальные в нижний. В MySQL отсутствует.

См. также:···
REPEAT('ab', 3)   -- ababab

Повторить строку N раз. Удобно для заполнителей и простых текстовых диаграмм.

См. также:···
REVERSE(name)

Перевернуть строку посимвольно. Иногда используют для индексации по суффиксу.

См. также:···
char_length
Статья
char_length(name), char_length('açai')   -- 4

Длина строки в СИМВОЛАХ (не байтах) — важно для UTF-8. Синоним character_length.

См. также:···
REGEXP_REPLACEPostgreSQL
Статья
REGEXP_REPLACE(phone, '[^0-9]', '', 'g')

Замена по регулярному выражению. Флаг 'g' — заменить все вхождения, иначе только первое.

См. также:·
REGEXP_MATCHESPostgreSQL
Статья
SELECT (REGEXP_MATCHES(url, '/(\d+)'))[1] AS id;

Вернуть захваченные группы регулярки как массив. С флагом 'g' даёт по строке на каждое совпадение.

См. также:·
REGEXP_SPLIT_TO_ARRAYPostgreSQL
Статья
REGEXP_SPLIT_TO_ARRAY('a, b,c', '\s*,\s*')

Разбить строку по регулярному разделителю в массив. Есть и …TO_TABLE — сразу в строки.

См. также:·
TRANSLATEPostgreSQL
Статья
TRANSLATE(code, 'abc', 'xyz')   -- a->x, b->y, c->z

Посимвольная замена по двум наборам. Лишние символы из первого набора удаляются. Не путать с REPLACE.

См. также:·
BTRIM / LTRIM / RTRIMPostgreSQL
Статья
BTRIM(code, '0'), LTRIM(s), RTRIM(s, '/')

Срезать заданные символы с обоих концов (BTRIM), слева (LTRIM) или справа (RTRIM); по умолчанию — пробелы.

См. также:·
FORMATPostgreSQL
Статья
FORMAT('Hi %s, id=%L', name, id)

Шаблонная сборка строки: %s — значение, %I — идентификатор, %L — безопасный литерал. Незаменимо в динамическом SQL.

См. также:····
STARTS_WITHPostgreSQL
Статья
WHERE STARTS_WITH(path, '/api/')

Начинается ли строка с префикса — читабельнее, чем LIKE 'x%'. Есть с PostgreSQL 11.

См. также:···
Код символаPostgreSQL
Статья
ascii('A')   -- 65
chr(65)      -- A

Код первого символа (ascii) и символ по коду (chr). В MySQL обратная функция называется CHAR.

См. также:··
to_hexPostgreSQL
Статья
to_hex(255)   -- 'ff'

Перевести целое число в шестнадцатеричную строку. Удобно для цветов, масок битов, отладки.

См. также:··

Числа и математика16

ROUND(3.14159)        -- 3
ROUND(2.5)            -- banker? no: 3

Округлить до ближайшего целого. Половинки округляются от нуля (2.5 → 3).

См. также:···
ROUND(x, n)
Статья
ROUND(3.14159, 2)     -- 3.14
ROUND(12345.6, -2)    -- 12300

Округлить до n знаков после запятой; отрицательный n округляет влево от точки. Работает только на numeric, не на float.

См. также:···
CEIL / CEILING
Статья
CEIL(4.1)   -- 5
CEIL(-4.1)  -- -4

Округление вверх до ближайшего целого. CEILING — синоним.

См. также:···
FLOOR(4.9)   -- 4
FLOOR(-4.1)  -- -5

Округление вниз до ближайшего целого. Для отрицательных уходит дальше от нуля.

См. также:···
TRUNCPostgreSQL
Статья
TRUNC(3.99)     -- 3
TRUNC(3.456, 2) -- 3.45

Отбросить дробную часть (к нулю), без округления. В MySQL это TRUNCATE(x, n).

См. также:···
ABS(-7)   -- 7

Модуль числа — абсолютное значение без знака.

См. также:···
MOD(10, 3)   -- 1

Остаток от деления. Полезен для «каждой N-й строки» и чётности; знак результата идёт за делимым.

См. также:···
POWER(2, 10)   -- 1024

Возведение в степень. POW — синоним.

См. также:··
SQRT(144)   -- 12

Квадратный корень. От отрицательного аргумента — ошибка.

См. также:··
EXP / LN
Статья
EXP(1)    -- 2.7182818...
LN(2.718) -- ~1

Экспонента e^x и натуральный логарифм (по основанию e). LN(0) и LN(отриц.) — ошибка.

См. также:··
LOGPostgreSQL
Статья
LOG(100)     -- 2  (base 10)
LOG(2, 8)    -- 3  (base 2)

В PostgreSQL LOG(x) — по основанию 10, LOG(b, x) — по произвольному основанию. Внимание: в MySQL LOG(x) — натуральный логарифм.

См. также:··
SIGN(-42)  -- -1
SIGN(0)    -- 0

Знак числа: -1, 0 или 1. Удобно для ветвления по направлению изменения.

См. также:···
GREATEST / LEAST
Статья
GREATEST(a, b, c), LEAST(a, b, c)

Максимум / минимум среди аргументов в одной строке (не агрегат). NULL-аргументы игнорируются.

См. также:···
RANDOMPostgreSQL
Статья
SELECT * FROM t ORDER BY RANDOM() LIMIT 5;

Случайное число в [0,1). В MySQL — RAND(). ORDER BY RANDOM() даёт случайную выборку, но дорог на больших таблицах.

См. также:
Целочисленное делениеPostgreSQL
Статья
DIV(7, 2)   -- 3
7 / 2       -- 3 when both are int

Целочисленное деление с отбрасыванием остатка. В PostgreSQL / уже делит нацело, если оба операнда целые; в MySQL для этого есть оператор DIV.

См. также:···
WIDTH_BUCKETPostgreSQL
Статья
WIDTH_BUCKET(score, 0, 100, 10)  -- bucket 1..10

Разложить значение по гистограммным корзинам равной ширины между двумя границами. Для распределений и группировки по диапазонам.

См. также:····

Функции даты и времени16

AGEPostgreSQL
Статья
AGE(end_ts, start_ts)   -- or AGE(birthday) vs now
AGE('2024-03-01', '2024-01-15')

Разница между двумя датами как интервал (годы/месяцы/дни), а не в секундах. С одним аргументом считает от текущей даты — удобно для возраста.

DATE_PARTPostgreSQL
Статья
DATE_PART('hour', created_at), DATE_PART('dow', created_at)

Функциональная форма EXTRACT — достаёт часть даты/времени числом. Поле задаётся строкой, поэтому удобно подставлять переменной.

См. также:···
Возраст/длительность в секундах
Статья
EXTRACT(EPOCH FROM (ended_at - started_at)) AS seconds

Перевести интервал или timestamp в секунды (Unix-время). Базовый способ померить длительность в секундах — затем дели на 60/3600.

См. также:···
TO_CHARPostgreSQL
Статья
TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI'), TO_CHAR(amount, 'FM999G999D00')

Отформатировать дату/число в строку по шаблону (YYYY, MM, DD, HH24...). Шаблоны в PostgreSQL отличаются от MySQL DATE_FORMAT.

См. также:·
TO_DATEPostgreSQL
Статья
TO_DATE('2024-03-15', 'YYYY-MM-DD')

Разобрать строку в дату по явному шаблону. Надёжнее, чем приведение ::date, когда формат нестандартный.

См. также:·
TO_TIMESTAMPPostgreSQL
Статья
TO_TIMESTAMP('2024-03-15 14:30', 'YYYY-MM-DD HH24:MI')
TO_TIMESTAMP(1710512400)   -- from Unix epoch

Разобрать строку в timestamp по шаблону, либо построить timestamptz из Unix-секунд (числовой аргумент).

См. также:·
CURRENT_TIMESTAMP / LOCALTIMESTAMP
Статья
SELECT CURRENT_TIMESTAMP, LOCALTIMESTAMP;

Время начала транзакции: CURRENT_TIMESTAMP с часовым поясом (timestamptz), LOCALTIMESTAMP — без. Не меняется в течение одной транзакции.

См. также:·
CURRENT_TIME / CURRENT_DATE
Статья
SELECT CURRENT_DATE, CURRENT_TIME;

Только сегодняшняя дата (CURRENT_DATE) или только время (CURRENT_TIME) — без скобок, это не функции, а специальные значения SQL.

См. также:·
Арифметика датPostgreSQL
Статья
SELECT order_date + 7, due_date - 1, end_dt - start_dt AS days;

К date можно прибавлять/вычитать целые дни (date + 7). Разность двух date даёт число дней (integer), двух timestamp — интервал.

См. также:·
make_date / make_timePostgreSQL
Статья
make_date(2024, 3, 15), make_time(14, 30, 0)

Собрать дату или время из отдельных чисел год/месяц/день, час/минута/секунда — без возни с форматными строками.

См. также:·
make_timestamp / make_intervalPostgreSQL
Статья
make_timestamp(2024, 3, 15, 14, 30, 0)
make_interval(days => 10, hours => 2)

Собрать timestamp или интервал из числовых компонентов. make_interval принимает именованные аргументы (days =>, hours =>).

См. также:·
AT TIME ZONEPostgreSQL
Статья
ts_utc AT TIME ZONE 'Europe/Moscow'
local_ts AT TIME ZONE 'UTC'

Перевести момент в другой часовой пояс. На timestamptz даёт локальное время в зоне; на timestamp без зоны — наоборот, трактует его как время в зоне.

См. также:··
JUSTIFY_INTERVAL / JUSTIFY_HOURSPostgreSQL
Статья
JUSTIFY_HOURS(INTERVAL '36 hours')   -- 1 day 12:00:00

Нормализовать интервал: перевести лишние часы в дни, дни в месяцы. Делает «50 hours» читаемым как «2 days 02:00:00».

См. также:··
DATE_BINPostgreSQL
Статья
DATE_BIN('15 minutes', ts, TIMESTAMP '2024-01-01')

Округлить timestamp вниз к началу бакета произвольной ширины (например, 15 минут) от опорной точки. Гибче DATE_TRUNC. Postgres 14+.

См. также:···
Пересечение периодов
Статья
(start_a, end_a) OVERLAPS (start_b, end_b)

Проверить, пересекаются ли два временных интервала. Удобно для поиска конфликтов бронирований/смен.

См. также:··
timestamptz vs timestampPostgreSQL
Статья
now()::timestamptz, '2024-03-15 10:00'::timestamp

timestamptz хранит момент в UTC и учитывает пояс при выводе; timestamp — «наивное» время без зоны. Для событий почти всегда нужен timestamptz.

См. также:··

CASE и NULL4

CASE WHEN
Статья
CASE
  WHEN score >= 90 THEN 'A'
  WHEN score >= 70 THEN 'B'
  ELSE 'C'
END

Условная логика прямо в SELECT — аналог if/else.

См. также:··
COALESCE
Статья
COALESCE(nickname, full_name, 'Anonymous')

Вернуть первое не-NULL значение из списка.

См. также:··
NULLIF(divisor, 0)

Превратить значение в NULL, если оно равно второму аргументу. Помогает избежать деления на ноль.

См. также:··
NULL и IS DISTINCT FROM
Статья
-- = NULL is never true — use IS NULL
WHERE deleted_at IS NULL
-- NULL-safe equality:
WHERE a IS DISTINCT FROM b

Сравнение с NULL через = всегда даёт «неизвестно» (не TRUE/FALSE). Для проверки — IS NULL; для NULL-безопасного равенства — IS DISTINCT FROM.

См. также:··

JSON / JSONB19

JSONB ->>PostgreSQL
Статья
payload->>'target'

Достать значение из JSONB как text — для подстановки в строки и сравнений.

См. также:··
JSONB @>PostgreSQL
Статья
payload @> '{"plan":"pro"}'

Содержит ли JSONB указанный фрагмент. Использует GIN-индекс — быстро на больших таблицах.

См. также:···
GIN + jsonb_path_opsPostgreSQL
Статья
CREATE INDEX idx ON events USING GIN (payload jsonb_path_ops)

Оптимальный индекс под @>-запросы по JSONB. Компактнее, чем дефолтный jsonb_ops.

См. также:
JSONB -> / ->>PostgreSQL
Статья
data->'user'->>'name'   -- -> keeps json, ->> as text

-> достаёт поле/элемент как jsonb (для дальнейшей навигации), ->> — как text. Ключ строкой, индекс массива числом.

См. также:··
Путь в JSONPostgreSQL
Статья
data #>> '{address,city}'   -- text at a nested path

Достать значение по вложенному пути массивом ключей: #> как jsonb, #>> как text. Короче цепочки ->.

См. также:··
JSONB_BUILD_OBJECTPostgreSQL
Статья
jsonb_build_object('id', id, 'name', name)

Собрать JSON-объект из чередующихся пар ключ, значение. Типы значений сохраняются (числа остаются числами).

См. также:····
JSONB_BUILD_ARRAYPostgreSQL
Статья
jsonb_build_array(id, name, created_at)

Собрать JSON-массив из переданных аргументов любых типов.

См. также:····
JSONB_AGGPostgreSQL
Статья
jsonb_agg(item ORDER BY created_at)

Агрегат: собрать строки группы в JSON-массив. ORDER BY внутри фиксирует порядок элементов.

См. также:····
JSONB_ARRAY_ELEMENTSPostgreSQL
Статья
SELECT e FROM t, jsonb_array_elements(t.tags) AS e

Развернуть JSON-массив в строки — одна строка на элемент. Вариант _text возвращает text вместо jsonb.

См. также:·····
JSONB_ARRAY_LENGTHPostgreSQL
Статья
jsonb_array_length(data->'items')

Длина JSON-массива. Ошибка, если значение не массив — оберни проверкой jsonb_typeof.

См. также:·····
JSONB_SETPostgreSQL
Статья
jsonb_set(data, '{address,city}', '"Lima"')

Вернуть копию JSON с заменённым значением по пути. create_missing=true (по умолчанию) добавляет ключ, если его нет.

См. также:·
JSONB_EACHPostgreSQL
Статья
SELECT key, value FROM jsonb_each(data)

Развернуть JSON-объект в строки (key, value) — по паре на ключ. Вариант _text даёт value как text.

См. также:·····
JSONB_OBJECT_KEYSPostgreSQL
Статья
SELECT jsonb_object_keys(data)

Вернуть имена ключей JSON-объекта верхнего уровня, по строке на ключ.

См. также:·····
Существование ключаPostgreSQL
Статья
data ? 'email'        -- has key?
data ?| array['a','b'] -- any of these keys?

Проверка наличия ключей: ? один ключ, ?| хотя бы один из, ?& все. Работают по верхнему уровню и опираются на GIN-индекс.

См. также:·····
Слияние JSONPostgreSQL
Статья
data || '{"verified":true}'

Слить два JSONB: правые ключи перезаписывают левые (мелкое слияние, без рекурсии). Удобно для частичного апдейта.

См. также:·
Удаление ключаPostgreSQL
Статья
data - 'temp'              -- drop a key
data #- '{address,zip}'    -- drop at a path

Удалить ключ/элемент: - по ключу или индексу верхнего уровня, #- по вложенному пути. Возвращает новый JSONB.

См. также:·
to_jsonbPostgreSQL
Статья
to_jsonb(row_var)   -- whole row as a json object

Превратить любое значение/строку/массив SQL в jsonb. Целую строку таблицы превращает в JSON-объект колонка → значение.

См. также:·····
JSONB_TYPEOFPostgreSQL
Статья
jsonb_typeof(data->'price')   -- 'number','string',...

Тип JSON-значения строкой: object, array, string, number, boolean, null. Полезно перед jsonb_array_length и т.п.

См. также:·····
JSONB_PRETTYPostgreSQL
Статья
jsonb_pretty(data)

Отформатировать JSONB с отступами для читаемого вывода/отладки.

См. также:·····

Производительность и индексы7

Частичный индексPostgreSQL
Статья
CREATE INDEX i ON orders (id) WHERE status = 'pending';

Индекс только по «горячему» подмножеству строк — компактнее и быстрее на скан.

См. также:····
Композитный индекс
Статья
CREATE INDEX i ON orders (customer_id, created_at DESC);

Покрывает фильтр + сортировку одним проходом. Порядок колонок важен.

См. также:····
Sargable WHERE
Статья
-- bad : WHERE EXTRACT(YEAR FROM ts) = 2024
-- good: WHERE ts >= '2024-01-01' AND ts < '2025-01-01'

Не оборачивай колонку в функцию — иначе индекс не подключится. Перепиши на диапазон.

См. также:····
NOT EXISTS vs NOT IN
Статья
-- NOT IN collapses to 0 rows on any NULL
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

NOT IN тихо ломается на любом NULL в подзапросе. NOT EXISTS — NULL-safe.

См. также:···
CONCURRENTLYPostgreSQL
Статья
CREATE INDEX CONCURRENTLY i ON events (user_id, kind);

Построить индекс на горячей таблице без жёсткого lock-а. Внутри транзакции запрещён.

См. также:····
EXPLAIN SELECT * FROM orders WHERE user_id = 5;

Показать план запроса без выполнения: какие сканы (Seq Scan / Index Scan), порядок соединений, оценка строк.

См. также:····
EXPLAIN (ANALYZE, BUFFERS)
Статья
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 5;

Выполнить запрос и показать РЕАЛЬНОЕ время и число строк против оценки. Большой разрыв оценка↔факт — признак устаревшей статистики или отсутствующего индекса.

См. также:····

Транзакции4

SELECT … FOR UPDATE
Статья
BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) FOR UPDATE;
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;

Залочить строки до конца транзакции. Стандарт для переводов между счетами.

См. также:··
UPDATE с условием
Статья
UPDATE accounts SET balance = balance - 200
WHERE id = 1 AND balance >= 200;

Атомарная проверка-и-обновление в одной команде. Если строки не обновились — на бэке возвращаем «недостаточно средств».

См. также:··
FOR UPDATE SKIP LOCKED
Статья
SELECT id FROM jobs
WHERE status = 'pending'
ORDER BY id
FOR UPDATE SKIP LOCKED LIMIT 1;

Очередь воркеров: каждый забирает свою джобу, не толкаясь на залоченных другими.

См. также:··
Race-safe инкремент
Статья
UPDATE counters SET n = n + 1 WHERE id = 1;

Один UPDATE безопасно инкрементит счётчик при параллельном доступе. SELECT-then-UPDATE теряет инкременты.

См. также:··

Доступы (GRANT/REVOKE)4

GRANT SELECT, INSERT ON orders TO analyst;

Выдать права на объект роли/пользователю. Перечисляем нужные действия (SELECT, INSERT, UPDATE, DELETE).

См. также:··
REVOKE INSERT ON orders FROM analyst;

Отозвать ранее выданные права.

См. также:··
Роль / пользователь
Статья
CREATE ROLE analyst LOGIN PASSWORD 'secret';

Создать роль (пользователя/группу). Права выдаются роли, а пользователи в неё включаются.

См. также:··
Read-only роль
Статья
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO readonly;

Типовой паттерн «только чтение»: доступ к схеме + SELECT на все таблицы + правило для будущих таблиц через ALTER DEFAULT PRIVILEGES.

См. также:··