Типы данных и NULL
Предпосылки: реляционная модель (таблица, строка, столбец, типизация столбцов).
← Реляционная модель | Выражения →
Тип столбца определяет не только «какие символы хранятся», но и какие операции допустимы и как они себя ведут. Числа можно складывать и сравнивать по величине. Текст можно искать по подстроке, но сложение бессмысленно (хотя конкатенация — склейка — возможна). А когда значение неизвестно, SQL использует специальный маркер NULL, который ведёт себя не так, как nil в Ruby или None в Python.
Зачем нужна строгая типизация
Допустим, данные о сотрудниках лежат в CSV-файле. Все столбцы — текст. Какие проблемы возникнут?
Зарплаты хранятся как строки. Сравнение '9000' > '70000' — TRUE, потому что текстовое сравнение работает посимвольно: '9' > '7'. При сортировке Евгений с зарплатой 55000 окажется перед Глебом с 70000 — '55000' < '70000', потому что '5' < '7', хотя числовое значение 55000 меньше 70000 лишь на 15000. Результат совпал случайно — текстовое сравнение не учитывает разрядность. Для корректного сравнения зарплат нужен числовой тип.
Цена товара записана как 19.99. После серии операций — сложений и умножений — вместо ожидаемых 199.90 получается 199.89999999999998. Числа с плавающей точкой (real, double precision) быстры, но неточны. Для денег нужен numeric — тип с точной десятичной арифметикой.
Дата найма записана как текст '15.03.2021'. Сортировка по этому столбцу не даст хронологический порядок — '15.03.2021' окажется перед '01.07.2020', потому что '1' < '0' — FALSE. Для дат нужен тип date, который понимает календарь.
Числовые типы
integer (или int) — целое число, 4 байта, диапазон примерно ±2 миллиарда. bigint — 8 байт, примерно ±9.2 × 10¹⁸. numeric(precision, scale) — число с точной десятичной арифметикой, без ошибок округления; используется для денег и финансовых расчётов. real и double precision — числа с плавающей точкой, быстрые, но с ошибками округления.
Практическое правило: для денег — numeric, для большинства задач — integer или bigint, для научных вычислений — double precision.
Текстовые типы
text — строка произвольной длины. Основной строковый тип в PostgreSQL. varchar(n) (от variable character, «строка переменной длины») — строка с ограничением длины (максимум n символов). char(n) — строка фиксированной длины, добивается пробелами; на практике используется редко.
В PostgreSQL text и varchar без указания длины работают практически одинаково1.
Дата и время
date — только дата: 2025-03-15. timestamp — дата и время без часового пояса (полное имя в стандарте SQL — timestamp without time zone). timestamptz — PostgreSQL-сокращение для timestamp with time zone — дата и время с учётом часового пояса: PostgreSQL хранит значение в UTC и конвертирует при выводе.
Практическое правило: почти всегда timestamptz, не timestamp. Без часового пояса рано или поздно появляются баги при смене зон или переходе на летнее время.
Boolean
boolean — логический тип: TRUE, FALSE и… NULL. Третье «значение» — не ошибка, а следствие того, как SQL обрабатывает отсутствие информации.
NULL — отсутствие значения
NULL в SQL означает «значение неизвестно». Не ноль, не пустая строка, не false — именно «мы не знаем». Если у сотрудника Дины salary = NULL, это не значит «зарплата ноль», это значит «мы не знаем, какая у неё зарплата».
id | name | salary
----+--------+--------
1 | Анна | 90000
2 | Борис | 60000
5 | Дина | NULL
В отличие от nil в Ruby (где nil == nil возвращает true) или None в Python, SQL-евский NULL — не значение, а маркер. Его нельзя сравнить ни с чем, включая другой NULL.
Трёхзначная логика
В обычной логике сравнение даёт true или false. В SQL появляется третий вариант — NULL. Это трёхзначная логика (three-valued logic).
Любое сравнение с NULL даёт NULL:
NULL = NULL --> NULL (не TRUE)
NULL = 1 --> NULL (не FALSE)
NULL > 5 --> NULL
NULL <> 5 --> NULL
Логика: если значение неизвестно, результат сравнения тоже неизвестен. Ментальная модель: NULL — это запечатанный конверт с неизвестным содержимым. Нельзя сказать, равно ли содержимое чему-то, пока конверт не вскрыт.
IS NULL — единственный способ проверки
Проверить, является ли значение NULL, можно только через IS NULL и IS NOT NULL:
SELECT name FROM employees WHERE salary IS NULL; name
------
Дина
Конструкция WHERE salary = NULL не работает — salary = NULL возвращает NULL для любой строки, и ни одна строка не проходит фильтр.
Логические операторы и NULL
Логические операторы AND, OR, NOT с NULL ведут себя по принципу «короткого замыкания»: если один операнд уже определяет результат, второй неважен.
TRUE AND NULL --> NULL -- результат зависит от неизвестного
FALSE AND NULL --> FALSE -- FALSE AND что-угодно = FALSE
TRUE OR NULL --> TRUE -- TRUE OR что-угодно = TRUE
FALSE OR NULL --> NULL -- результат зависит от неизвестного
NOT NULL --> NULL -- отрицание неизвестности = неизвестность
Принцип: если один операнд уже решает исход независимо от второго — результат определён. Если нет — результат NULL.
WHERE и NULL
WHERE пропускает строку только если результат условия — TRUE. И NULL, и FALSE отсеиваются одинаково.
SELECT name FROM employees WHERE salary <> 90000; name
---------
Борис
Вера
Глеб
Евгений
Дина не попала: NULL <> 90000 даёт NULL, строка отсеяна. Если нужны строки с неизвестной зарплатой, условие нужно дополнить:
WHERE salary <> 90000 OR salary IS NULLЭто правило всплывает снова и снова: NULL проваливает любое сравнение, и строка молча исчезает из результата.
NULL в разных контекстах
NULL ведёт себя по-разному в зависимости от контекста — это одна из самых контринтуитивных особенностей SQL:
| Контекст | Два NULL считаются… | Почему |
|---|---|---|
= в WHERE | несравнимыми (NULL = NULL ⇒ NULL) | оператор сравнения |
| DISTINCT | одинаковыми (дубликат) | специальное правило дедупликации |
| GROUP BY | одной группой | аналогично DISTINCT |
| ORDER BY | «наибольшими» (в PostgreSQL по умолчанию) | конвенция сортировки, переопределяемая через NULLS FIRST/LAST |
DISTINCT и GROUP BY используют не обычное сравнение =, а механизм «IS NOT DISTINCT FROM», который считает два NULL одинаковыми. Это непоследовательно с поведением =, но так определён стандарт SQL.
Подробности поведения NULL в каждом контексте — в соответствующих файлах querying/.
Sources
- PostgreSQL Documentation (v16): Data Types. https://www.postgresql.org/docs/16/datatype.html
- C.J. Date, 2011, SQL and Relational Theory — подробный разбор трёхзначной логики.
← Реляционная модель | Выражения →
Footnotes
-
В стандарте SQL и в ряде СУБД (MySQL, Oracle, SQL Server) типы
TEXT/CLOBисторически были ограничены: нельзя использовать в индексах, вGROUP BY, сравнивать через=. В PostgreSQLtext— полноценный тип без таких ограничений, внутренне идентичныйvarcharбез указания длины. ↩