SELECT: παίρνουμε τα δεδομένα που χρειαζόμαστε

NULL: όταν η τιμή απλώς δεν υπάρχει

23 min
What you'll learn
  • να ξεχωρίζεις το NULL από το μηδέν και την κενή συμβολοσειρά και να το ελέγχεις μέσω IS NULL / IS NOT NULL
  • να εξηγείς την τριαδική λογική: γιατί το x = NULL δίνει και το WHERE δεν αφήνει να περάσει τέτοια γραμμή
  • να βρίσκεις την αιτία ενός κενού αποτελέσματος από NOT IN με λίστα στην οποία κρύβεται ένα NULL
  • να βάζεις εφεδρικές τιμές στο αποτέλεσμα μέσω COALESCE, χωρίς να αλλάζεις τα δεδομένα στον πίνακα

Το NULL σημαίνει «άγνωστο»

Στις ολογραφίες των παλιών αρχείων υπάρχουν κενά: ένα κελί δεν φωτίζεται, και μέσα του βλέπεις το σκοτάδι. Δεν είναι ούτε φθορά ούτε ίχνος συντήρησης. Το αρχείο είναι έντιμο: εκεί όπου δεν ήξερε την απάντηση, δεν την επινόησε. Το «δεν ξέρω» δεν είναι «όχι».

Το NULL δεν είναι μηδέν ούτε κενή συμβολοσειρά, αλλά η απουσία τιμής. Στο «Kotomarket» αυτό μπορεί να μοιάζει, για παράδειγμα, με τη μη συμπληρωμένη πόλη ενός χρήστη. Η SQL δεν το θεωρεί συνηθισμένη τιμή, γι' αυτό η έκφραση x = NULL δεν δίνει ούτε TRUE ούτε FALSE, αλλά — την τρίτη απάντηση της τριαδικής λογικής.

Γι' αυτό ελέγχεις ως εξής:

  • στήλη IS NULL — η τιμή απουσιάζει·
  • στήλη IS NOT NULL — η τιμή υπάρχει.

Αν στο αποτέλεσμα ένα κενό φαίνεται υπερβολικά αινιγματικό, βάλε ένα εφεδρικό κείμενο μέσω COALESCE(στήλη, εφεδρικό). Αυτό δεν διορθώνει τα δεδομένα στον πίνακα, αλλά παρουσιάζει προσεγμένα το αποτέλεσμα — σαν την πινακίδα «δεν υπάρχουν στοιχεία» σε μια άδεια βιτρίνα.

QUERY: Σεβάσου τις τρύπες στη μνήμη. Μια βάση που λέει έντιμα «δεν ξέρω» είναι πιο αξιόπιστη από μια βάση που λέει ψέματα με σιγουριά.

NULL, ноль и пустая строка — это разные вещи

Новички часто воспринимают NULL как «пустоту», а потом удивляются, почему запросы работают странно.

Разберём на простом примере.

Допустим, в таблице products есть столбец discount_percent:

idtitlediscount_percent
1Корм «Лунный тунец»10
2Миска антигравитационная0
3Лежанка архивиста

Что означает каждая строка?

У первого товара скидка 10%.

У второго товара скидка 0%. Это значит: скидка известна, она равна нулю.

У третьего товара стоит NULL. Это значит: мы не знаем скидку, она не указана, не рассчитана или пока не загружена.

То есть:

discount_percent = 0

и

discount_percent IS NULL

— это разные состояния.

То же самое с текстом.

city = ''

означает: город указан как пустая строка.

city IS NULL

означает: города в ячейке вообще нет.

В реальных базах это важно. Например, пустая строка может появиться из формы, где пользователь стёр текст и отправил поле пустым. А NULL может означать, что поле вообще не заполняли, данные ещё не пришли из другого сервиса или значение действительно неизвестно.

idfull_namecity1AnnaMoscow2IvanNULL3MariaKazanIvan has no city — the cell is NULL
Η δεύτερη γραμμή δεν έχει πόλη — εκεί βρίσκεται ένα NULL. Δεν είναι κενή συμβολοσειρά ούτε μηδέν, αλλά απουσία τιμής.

Почему = NULL не работает

Теперь главный подвох.

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

SELECT id, name, city
FROM users
WHERE city = NULL;

Но такой запрос не найдёт строки с NULL.

Причина в том, что NULL — это не обычное значение. Его нельзя сравнивать через = так же, как число или строку.

SQL рассуждает примерно так:

city = 'Москва'

Можно проверить. Если в city лежит 'Москва', ответ TRUE. Если лежит 'Казань', ответ FALSE.

А вот так:

city = NULL

SQL не может ответить TRUE или FALSE, потому что NULL означает «значение неизвестно».

Если город неизвестен, можно ли сказать, что он равен NULL? Нет.
Можно ли сказать, что он не равен NULL? Тоже нет.

Ответ SQL: .

То есть «неизвестно».

В SQL есть не только два логических результата:

TRUE
FALSE

Есть ещё третий:

UNKNOWN

Это называется трёхзначная логика.

TRUEdefinitely yesFALSEdefinitely noUNKNOWN?unknownx = NULLUNKNOWNWHERE keeps a row only when TRUE
Οι τρεις απαντήσεις του αρχείου: TRUE, FALSE και UNKNOWN — οποιαδήποτε σύγκριση με NULL μέσω = ή <> δίνει την τρίτη, και το WHERE δεν αφήνει να περάσει τέτοια γραμμή.
Сравнения с NULL ведут себя особенно. Обычные операторы = и <> не дают ни TRUE, ни FALSE: результатом становится NULL в выдаче, то есть логическое UNKNOWN.
Query result
eq_nullneq_nullnull_eq_nullis_null
true

WHERE пропускает только TRUE

Теперь важный шаг.

WHERE оставляет строку только тогда, когда условие вернуло TRUE.

Если условие вернуло FALSE, строка отбрасывается.

Если условие вернуло UNKNOWN, строка тоже отбрасывается.

То есть для WHERE работает правило:

Результат условияСтрока попадёт в результат?
TRUEда
FALSEнет
нет

Именно поэтому запрос:

SELECT id, name, city
FROM users
WHERE city = NULL;

не возвращает пользователей без города.

Для строки, где city равен NULL, выражение:

city = NULL

даёт не TRUE, а UNKNOWN.

А WHERE пропускает только TRUE.

Как правильно проверять NULL

Для NULL есть специальные проверки:

столбец IS NULL

и

столбец IS NOT NULL

Если нужно найти пользователей, у которых город не указан:

SELECT id, name, city
FROM users
WHERE city IS NULL;

Если нужно найти пользователей, у которых город указан:

SELECT id, name, city
FROM users
WHERE city IS NOT NULL;

Запомни главное правило:

-- неправильно
WHERE city = NULL

-- неправильно
WHERE city <> NULL

-- правильно
WHERE city IS NULL

-- правильно
WHERE city IS NOT NULL

IS NULL не сравнивает значение с NULL. Он задаёт другой вопрос:

В этой ячейке значение отсутствует?

А IS NOT NULL спрашивает:

В этой ячейке значение есть?

IS NULL и IS NOT NULL — правильный способ фильтровать строки по отсутствию или наличию значения.
Query result
The query returned no rows

Почему <> тоже может удивить

Ещё один частый баг появляется с оператором <>.

Допустим, нужно найти всех пользователей не из Москвы.

Новичок пишет:

SELECT id, name, city
FROM users
WHERE city <> 'Москва';

Кажется, что запрос должен вернуть:

  • пользователей из Казани
  • пользователей из других городов
  • пользователей, у которых город не указан

Но строки с NULL в результат не попадут.

Почему?

Для строки с городом 'Казань':

city <> 'Москва'

даёт TRUE.

Для строки с городом 'Москва':

city <> 'Москва'

даёт FALSE.

Для строки с NULL:

city <> 'Москва'

даёт UNKNOWN.

А WHERE пропускает только TRUE.

Поэтому строка с неизвестным городом отбрасывается.

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

SELECT id, name, city
FROM users
WHERE city <> 'Москва'
   OR city IS NULL;

Теперь логика такая:

Покажи всех, у кого город не Москва, а также тех, у кого город вообще не указан.

В PostgreSQL есть ещё удобный оператор:

WHERE city IS DISTINCT FROM 'Москва'

Он обращается с NULL более предсказуемо и возвращает строго TRUE или FALSE.

Например:

NULL IS DISTINCT FROM 'Москва'

вернёт TRUE.

А:

NULL IS NOT DISTINCT FROM NULL

вернёт TRUE.

Но базовое правило для новичка остаётся таким: если нужно работать с отсутствующим значением, используй IS NULL и IS NOT NULL.

<> не включает строки с NULL, потому что сравнение с неизвестным значением даёт UNKNOWN.
Query result
idfull_namecity
1Артём ВолковСанкт-Петербург
2Екатерина АлексеевЕкатеринбург
3Николай НикитинАлматы
4Екатерина ИвановНовосибирск
5Елена ПетровКазань
6Ирина КузнецовНовосибирск
7Наталья СмирновНижний Новгород
8Николай ЗайцевНовосибирск
10Алексей ЛебедевЕкатеринбург
11Дмитрий КозловАлматы
12Павел СмирновНижний Новгород
13Алексей ЛебедевСанкт-Петербург
14Анна НикитинНижний Новгород
15Екатерина МакаровКазань
18Дмитрий ИвановМинск
19Светлана ВолковНовосибирск
20Ирина ОрловСанкт-Петербург
22Ирина ПетровЕкатеринбург
23Светлана СоколовЕкатеринбург
24Елена ЗайцевСанкт-Петербург

Осторожно с NOT, AND и OR

NULL особенно часто ломает ожидания в сложных условиях.

Посмотри на условие:

WHERE NOT (city = 'Москва')

На первый взгляд оно похоже на:

WHERE city <> 'Москва'

И действительно, для обычных значений результат будет похож.

Но если city равен NULL, выражение:

city = 'Москва'

даёт UNKNOWN.

А теперь важный момент:

NOT UNKNOWN

тоже даёт UNKNOWN.

Не TRUE.

Поэтому строка с NULL всё равно не попадёт в результат.

Пример:

SELECT id, name, city
FROM users
WHERE NOT (city = 'Москва');

Строки с неизвестным городом не пройдут.

Чтобы включить их, нужно писать явно:

SELECT id, name, city
FROM users
WHERE city <> 'Москва'
   OR city IS NULL;

Это правило стоит запомнить:

NOT не превращает UNKNOWN в TRUE.

В SQL UNKNOWN остаётся неизвестностью даже после отрицания.

COALESCE: запасной свет для прорех

Иногда NULL в данных — нормальная ситуация, но в отчёте его неудобно показывать пользователю.

Например:

SELECT id, name, city
FROM users;

Результат:

idnamecity
1АннаМосква
2Борис

Для внутренней базы NULL понятен. Но в интерфейсе или отчёте лучше показать человеку нормальную подпись:

город не указан

Для этого используют COALESCE.

COALESCE возвращает первый аргумент, который не равен NULL.

SELECT COALESCE(NULL, NULL, 'запасной вариант');

Результат:

coalesce
запасной вариант

Применим к таблице:

SELECT
    id,
    name,
    COALESCE(city, 'город не указан') AS city_for_report
FROM users;

Результат:

idnamecity_for_report
1АннаМосква
2Борисгород не указан
3ВераКазань

Важно: COALESCE не меняет данные в таблице.

Он меняет только выдачу запроса.

То есть в таблице у Бориса по-прежнему хранится NULL. Просто в результате запроса мы показываем вместо него понятный текст.

Это похоже на табличку на пустой витрине: не товар появился, а объяснение для зрителя.

COALESCE помогает заменить NULL запасным значением в результате запроса, не меняя исходные данные.
Query result
idfull_namecity
1Артём ВолковСанкт-Петербург
2Екатерина АлексеевЕкатеринбург
3Николай НикитинАлматы
4Екатерина ИвановНовосибирск
5Елена ПетровКазань
6Ирина КузнецовНовосибирск
7Наталья СмирновНижний Новгород
8Николай ЗайцевНовосибирск
9Ирина КозловМосква
10Алексей ЛебедевЕкатеринбург
11Дмитрий КозловАлматы
12Павел СмирновНижний Новгород
13Алексей ЛебедевСанкт-Петербург
14Анна НикитинНижний Новгород
15Екатерина МакаровКазань
16Елена КозловМосква
17Наталья МакаровМосква
18Дмитрий ИвановМинск
19Светлана ВолковНовосибирск
20Ирина ОрловСанкт-Петербург

COALESCE может выбирать из нескольких вариантов

COALESCE принимает не два аргумента, а сколько угодно.

Он идёт слева направо и возвращает первый аргумент, который не NULL.

Например, в таблице пользователей могут быть разные способы связи:

idnametelegramemailphone
1Анна@annaanna@mail.test
2БорисNULLboris@mail.testNULL
3ВераNULLNULL+7001

Мы хотим показать лучший доступный контакт: сначала Telegram, если он есть; если нет — email; если нет — телефон; если нет ничего — текст «нет контакта».

SELECT
    id,
    name,
    COALESCE(telegram, email, phone, 'нет контакта') AS best_contact
FROM users;

Результат:

idnamebest_contact
1Анна@anna
2Борисboris@mail.test
3Вера+7001

Логика такая:

возьми telegram
если telegram NULL — возьми email
если email NULL — возьми phone
если phone NULL — возьми 'нет контакта'

Это очень частый паттерн в отчётах, выгрузках и интерфейсах.

Важный технический момент: аргументы COALESCE должны быть совместимы по типам.

Так можно:

COALESCE(city, 'город не указан')

Потому что оба варианта — текст.

А вот так может быть ошибка:

COALESCE(discount_percent, 'нет скидки')

Если discount_percent — число, а 'нет скидки' — текст, базе может быть непонятно, какой тип результата нужен.

Обычно в таких случаях число сначала явно превращают в текст:

COALESCE(discount_percent::text, 'нет скидки')

NULL в SELECT и NULL в WHERE — разные ощущения

Важно различать две ситуации.

В SELECT ты можешь увидеть NULL в результате:

SELECT id, name, city
FROM users;

Здесь NULL просто выводится как значение ячейки в результате. В разных SQL-редакторах он может выглядеть по-разному: как NULL, как пустая ячейка или как специальная метка.

А вот в WHERE NULL влияет на фильтрацию:

SELECT id, name, city
FROM users
WHERE city <> 'Москва';

Здесь строка с NULL может исчезнуть из результата, потому что условие стало UNKNOWN.

То есть SELECT показывает данные, а WHERE решает, пропускать строку или нет.

Для WHERE важно не то, как красиво выглядит ячейка, а вернуло ли условие строго TRUE.

Частые ошибки с NULL

Ошибка 1. Искать NULL через =

WHERE city = NULL

Так не нужно писать. Это условие не найдёт строки с NULL.

Правильно:

WHERE city IS NULL

Ошибка 2. Искать заполненные значения через <> NULL

WHERE city <> NULL

Так тоже не нужно писать. Это условие не найдёт заполненные строки.

Правильно:

WHERE city IS NOT NULL

Ошибка 3. Думать, что <> включает NULL

WHERE city <> 'Москва'

Этот запрос найдёт города, которые точно не равны Москве. Но строки, где город неизвестен, он не вернёт.

Если нужны и «не Москва», и «город не указан»:

WHERE city <> 'Москва'
   OR city IS NULL

Ошибка 4. Путать пустую строку и NULL

WHERE city = ''

Это ищет пустую строку, а не NULL.

Чтобы найти оба варианта:

WHERE city = ''
   OR city IS NULL

Иногда в грязных данных приходится проверять и то, и другое.

Ошибка 5. Использовать COALESCE и думать, что данные изменились

SELECT COALESCE(city, 'город не указан') AS city
FROM users;

Этот запрос только меняет отображение результата.

Он не записывает 'город не указан' в таблицу.

Чтобы реально изменить данные, нужен UPDATE, но это уже отдельная операция и отдельная ответственность.

Главное правило урока

NULL нельзя проверять через обычные сравнения.

Пиши так:

WHERE column IS NULL

или так:

WHERE column IS NOT NULL

Не пиши так:

WHERE column = NULL

и так:

WHERE column <> NULL

WHERE пропускает только строки, где условие вернуло TRUE.

FALSE и UNKNOWN в результат не проходят.

Interview question

Ερώτηση συνέντευξης: γιατί το WHERE city <> 'Москва' δεν θα επιστρέψει τις γραμμές όπου η πόλη δεν είναι συμπληρωμένη, και πώς τις βάζεις στο αποτέλεσμα;

Δυνατή απάντηση: το NULL <> 'Москва' δίνει , και το WHERE αφήνει να περάσει μόνο το TRUE — οι γραμμές με αποκόπτονται τόσο από το = όσο και από το <>. Για να τις λάβεις υπόψη γράφεις WHERE city <> 'Москва' OR city IS NULL, ή στην PostgreSQL WHERE city IS DISTINCT FROM 'Москва': αυτός ο τελεστής μεταχειρίζεται το NULL σαν συνηθισμένη τιμή και επιστρέφει αυστηρά TRUE ή FALSE.

Ερώτηση συνέντευξης: σε τι διαφέρει το NULL από το μηδέν και την κενή συμβολοσειρά;

Δυνατή απάντηση: το μηδέν και η κενή συμβολοσειρά είναι τιμές: είναι ίσες με τον εαυτό τους και συμμετέχουν σε συγκρίσεις. Το NULL είναι το σημάδι «δεν υπάρχει τιμή»: οποιαδήποτε σύγκριση μαζί του μέσω =/<> δίνει UNKNOWN, ακόμη και το NULL = NULL· ο έλεγχος είναι δυνατός μόνο μέσω IS NULL/IS NOT NULL. Ξεχωριστή ιστορία είναι η Oracle, όπου μια κενή συμβολοσειρά σε VARCHAR2 αποθηκεύεται ως NULL: κλασική πηγή σφαλμάτων κατά τη μεταφορά κώδικα.

Check yourself
Как правильно проверить, что значения в столбце нет?
Check yourself
Что вернёт выражение NULL = NULL?
Check yourself
Какие строки пропускает WHERE?
Check yourself
Что делает COALESCE(city, 'город не указан')?

КВЕРИ: Пустота тоже говорит. Главное — не заставлять её притворяться значением.