Навчальний сценарій: адміністративний інструмент має показати сторінки, яких немає у списку виключень. До списку потрапляє один незаповнений ідентифікатор, і раптом результат порожній. Помилки синтаксису немає, сторінки не видалені, кеш тут ні до чого. Поєднання NOT IN і NULL у SQL може зробити умову невідомою замість істинної. Нижче розберемо це на трьох умовних сторінках, без доступу до робочої бази.
Мета — не механічно замінити один оператор іншим, а визначити правильне правило виключення. Потрібно побачити проміжний результат, отримати очікуваний перелік і перевірити крайові випадки. Такий розбір корисний для фільтрів каталогу, списків завдань і службових звітів. Усі ідентифікатори вигадані; приклад не описує реального клієнта й не доводить, що саме ця причина є у вашому проєкті.
Спочатку покажіть результат умови, а не приховуйте його
Відкрийте SQL-редактор із локальним тестовим SQLite, що підтримує WITH і VALUES. Додаткові розширення, права адміністратора та створення таблиць не потрібні. Каталог запуску не має значення: код виконується в редакторі SQL, а не в командному рядку оболонки. Назви pages та excluded — локальні вирази поточного запиту; вони не читають фізичні таблиці сайту. Не підставляйте справжні клієнтські дані у публічні SQL-пісочниці.
WITH pages(id) AS (
VALUES (101), (102), (103)
), excluded(page_id) AS (
VALUES (101), (NULL)
)
SELECT id,
id NOT IN (SELECT page_id FROM excluded) AS allowed
FROM pages
ORDER BY id;
У SQLite очікуйте три рядки: для 101 поле allowed дорівнює 0, а для 102 і 103 має значення NULL. Деякі клієнти показують NULL як порожню клітинку: перевірте налаштування відображення й не називайте її порожнім рядком. Цей результат показує різницю між «знайдено у виключеннях» і «не вдалося підтвердити відсутність». Він ще не є кінцевим списком сторінок.
Список містить 101, тому цю сторінку виключено однозначно. Для інших ідентифікаторів невідома частина списку не дозволяє отримати істинний результат NOT IN. Правила та таблицю результатів для різних випадків наведено в документації SQLite. Не прирівнюйте NULL до нуля: це змінить зміст даних, а не виправить умову.
Зіставте порожню вибірку з навчальними даними
WITH pages(id) AS (
VALUES (101), (102), (103)
), excluded(page_id) AS (
VALUES (101), (NULL)
)
SELECT id
FROM pages
WHERE id NOT IN (SELECT page_id FROM excluded)
ORDER BY id;
Цей самодостатній запит повторює ті самі навчальні рядки, але переносить умову у WHERE. Очікуваний результат — жодного рядка. Відсутність сторінок тут не доводить, що всі вони внесені до виключень: WHERE не пропускає невідомий результат умови. Саме тому варто спочатку вивести обчислене поле, а вже потім використовувати його як фільтр.
Зробіть контрольну зміну лише у тексті навчального запиту: приберіть рядок (NULL) разом із зайвою комою, залишивши VALUES (101). Тепер очікуйте 102 та 103. Поверніть NULL і повторіть виконання. Така пара перевірок відділяє поведінку оператора від випадкових обмежень за датою, статусом чи правами. Вона нічого не змінює у збережених таблицях, тому відновлювати базу після вправи не потрібно.
У робочому застосунку спершу дослідіть, звідки з’являється невизначений ідентифікатор: незавершений імпорт, необов’язкове поле чи помилка формування підзапиту. Це гіпотези для перевірки, а не встановлені причини. Не видаляйте такі записи навмання і не замінюйте їх довільним числом. Збережіть кілька знеособлених прикладів та погодьте, чи можна ігнорувати невизначений зв’язок.
Виразіть відсутність відповідності через NOT EXISTS
WITH pages(id) AS (
VALUES (101), (102), (103)
), excluded(page_id) AS (
VALUES (101), (NULL)
)
SELECT p.id
FROM pages AS p
WHERE NOT EXISTS (
SELECT 1
FROM excluded AS e
WHERE e.page_id = p.id
)
ORDER BY p.id;
Третій запит повертає 102 та 103: для кожної сторінки він перевіряє, чи немає відповідного рядка у виключеннях. Рядок із невідомим page_id не створює відповідності за рівністю. Імена p та e — лише псевдоніми, але умова e.page_id = p.id обов’язкова. Якщо забути цей зв’язок, підзапит почне відповідати на інше питання, і результат знову може стати порожнім.
Це не універсальний дозвіл ігнорувати NULL. Якщо невідоме виключення означає «перевірку ще не завершено», правильна поведінка інструмента може полягати в окремому попередженні або зупиненні операції. Визначте це до виправлення. NOT EXISTS зручний тоді, коли правило справді звучить як «немає рядка з таким ідентифікатором». Семантику підзапитів також пояснює документація PostgreSQL; сумісність свого середовища перевіряйте окремо.
Інший можливий підхід — відфільтрувати NULL у підзапиті для NOT IN через WHERE page_id IS NOT NULL. Він придатний лише за погодженої політики ігнорування таких значень. Окремо визначте поведінку для NULL зліва, тобто у pages.id: NOT IN і NOT EXISTS не слід вважати безумовно взаємозамінними. У нашому початковому наборі всі ідентифікатори сторінок задані.
Закріпіть правило перевірками до зміни застосунку
Збережіть початкову несправну вибірку й виправлений варіант як окремі тести. Перевірте список без NULL, список із NULL, повторення 101, відсутність відповідних ідентифікаторів та повністю порожній підзапит. Для кожного випадку наперед запишіть очікувані ID. Порівнюйте самі ідентифікатори, а не тільки кількість: одна пропущена й одна зайва сторінка можуть дати правдоподібний підсумок.
Перш ніж переносити правило в код сайту, збережіть попередню версію й перегляньте зміни в Git. Використовуйте тестову копію та обліковий запис із правами лише читання. Великий SELECT теж може створити навантаження, тому не запускайте необмежений підзапит у продуктивній базі для експерименту. Не обіцяйте прискорення від самої заміни оператора: продуктивність перевіряється на вашій схемі й даних.
Висновок: порожній результат NOT IN не завжди означає відсутність потрібних сторінок. Покажіть значення умови, перевірте NULL і сформулюйте правило відповідності явно. Якщо службовий список або фільтр сайту приховує записи без очевидної причини, зверніться до AriZone з мінімальним знеособленим прикладом та очікуваними ID — це допоможе почати з перевірюваної логіки, не змінюючи реальні дані навмання.
Залиште коментар