Перейти к содержимому

Фильтрация строк: WHERE и ловушка NULL

Курс MS SQL с нуля · Урок 3 из 12 · Часть 1. Среда и первые запросы

WHERE — самая нагруженная часть запроса на практике и главный источник логических ошибок у новичков. Урок разбирает сравнения, списки, шаблоны и трёхзначную логику NULL.

Перед уроком база обязана быть поднята скриптами урока 2; урок только читает данные и ничего не меняет.

Содержание

1. Что вы освоите

К концу урока вы:

  • строите предикаты равенства, диапазона и принадлежности списку;
  • ищете подстроки шаблонами LIKE с процентами и подчёркиваниями;
  • объясняете трёхзначную логику NULL и пишете IS NULL корректно;
  • доказываете полноту фильтра суммой обеих его половин;
  • ставите скобки, когда рядом AND и OR.
↑ К оглавлению

2. Операторы сравнения и отрицание

Равенство пишется одиночным знаком «равно», диапазоны — через BETWEEN; замысел «не равно» выражается отрицанием равенства:

Чистый код

WHERE Price BETWEEN 1000 AND 2000
WHERE Status = N'Paid'
WHERE NOT (Status = N'Cancelled')
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    WHERE Price BETWEEN 1000 AND 2000
    -- → диапазон от тысячи до двух тысяч, обе границы включаются
    WHERE Status = N'Paid'
    -- → равенство одиночным знаком «равно», литерал с префиксом N
    WHERE NOT (Status = N'Cancelled')
    -- → каноничное отрицание курса: NOT плюс равенство в скобках
    SQL
Префикс N: литералы с кириллицей начинаются с N: N'Москва' — указание Unicode-типа; без него возможны замены символов на вопросительные знаки.

Списки и шаблоны

IN проверяет принадлежность списку, LIKE — шаблону: процент — любое число любых символов, подчёркивание — ровно один символ:

Чистый код

WHERE Status IN (N'New', N'Paid')
WHERE SKU LIKE N'SKU-00___'
WHERE Attributes LIKE N'%black%'
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    WHERE Status IN (N'New', N'Paid')
    -- → принадлежность списку: равно хотя бы одному из значений
    WHERE SKU LIKE N'SKU-00___'
    -- → пять подчёркиваний: ровно пять любых знаков на хвосте артикула
    WHERE Attributes LIKE N'%black%'
    -- → проценты с обеих сторон: слово встречается в любом месте строки
    SQL
Мина NOT IN: если в списке или подзапросе NOT IN окажется NULL, условие не пропустит ни одной строки целиком. Подробный разбор — в уроке 6; в этом уроке запоминаем: списки NOT IN строят без NULL.
↑ К оглавлению

3. Ловушка NULL и приём полноты

NULL — «значения нет», а не ноль и не пустая строка. Любое сравнение с NULL даёт НЕИЗВЕСТНО, а WHERE пропускает строку только при ИСТИНА. Следствия:

  • WHERE Email = NULL не вернёт ничего никогда; правильно — IS NULL и IS NOT NULL;
  • WHERE NOT (Segment = N'VIP') теряет строки, где Segment IS NULL: если NULL-строки нужны, добавляют OR Segment IS NULL;
  • проверка фильтра: сумма половин «выполняется» и «не выполняется» обязана равняться всей таблице.

Приём полноты

универсальная диагностика фильтров

Чистый код

SELECT COUNT(*) FROM dbo.Products WHERE Attributes IS NULL;
SELECT COUNT(*) FROM dbo.Products WHERE Attributes IS NOT NULL;
SELECT COUNT(*) FROM dbo.Products;
полнота: А + Б = 500
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    SELECT COUNT(*) FROM dbo.Products WHERE Attributes IS NULL;
    -- → половина А: сколько товаров без атрибутов
    SELECT COUNT(*) FROM dbo.Products WHERE Attributes IS NOT NULL;
    -- → половина Б: сколько с атрибутами
    SELECT COUNT(*) FROM dbo.Products;
    -- → целое, ровно 500; фильтр исправен, если А плюс Б равно 500
    полнота: А + Б = 500
↑ К оглавлению

4. Приоритет AND и OR

AND связывает сильнее OR. Условие «активные товары дешевле тысячи или из категории 5» без скобок читается сервером как «(активные и дешёвые) или (категория)» — это другая выборка:

Чистый код

WHERE IsActive = 1 AND Price BETWEEN 0 AND 999 OR CategoryID = 5
WHERE (IsActive = 1 AND Price BETWEEN 0 AND 999) OR CategoryID = 5
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    WHERE IsActive = 1 AND Price BETWEEN 0 AND 999 OR CategoryID = 5
    -- → антипример: сервер читает как (активные и дешёвые) или (категория 5)
    WHERE (IsActive = 1 AND Price BETWEEN 0 AND 999) OR CategoryID = 5
    -- → эталон: скобки фиксируют, что условие по категории — отдельная ветка
    SQL

Даты как диапазоны

OrderDate — DATETIME2: кроме даты там время. Фильтр «за один день» пишется включительным диапазоном от полуночи до последней секунды дня:

Чистый код

WHERE OrderDate BETWEEN '2026-10-23 00:00:00' AND '2026-10-23 23:59:59'
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    WHERE OrderDate BETWEEN '2026-10-23 00:00:00' AND '2026-10-23 23:59:59'
    -- → весь день: от полуночи до последней секунды; DATETIME2 хранит и время
    SQL
Куда расти: в продвинутом курсе диапазоны дат переписывают полузакрытой формой; включительная выбрана здесь как самая буквальная для первого знакомства.
↑ К оглавлению

5. Задачи и эталонные решения

Семь задач на контексте TechStore.

  1. товары с ценой от тысячи до двух тысяч включительно, дешёвые сначала;
  2. заказы в статусах New, Paid и Shipped одним IN без цепочки OR;
  3. товары, в JSON-атрибутах которых встречается слово black;
  4. все клиенты с общими адресами вида Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. — шаблон с якорем;
  5. полнота фильтра Attributes: NULL, NOT NULL и их сумма против всей таблицы;
  6. все заказы самого свежего дня данных: дата берётся из MAX(OrderDate);
  7. вариант условия «активные и дешёвые или категория 5» без скобок и со скобками: разница и объяснение.

Эталоны урока

открывать после самостоятельной попытки

Чистый код

SELECT ProductID, Name, Price
FROM dbo.Products
WHERE Price BETWEEN 1000 AND 2000
ORDER BY Price;
GO

SELECT OrderID, OrderDate, Status
FROM dbo.Orders
WHERE Status IN (N'New', N'Paid', N'Shipped');
GO

SELECT ProductID, Name, Attributes
FROM dbo.Products
WHERE Attributes LIKE N'%black%'
ORDER BY Name;
GO

SELECT CustomerID, FullName, Email
FROM dbo.Customers
WHERE Email LIKE N'shared%@example.com';
GO

SELECT MAX(OrderDate) AS last_order_at FROM dbo.Orders;
GO

SELECT OrderID, OrderDate, Status, TotalAmount
FROM dbo.Orders
WHERE OrderDate BETWEEN '2026-10-23 00:00:00' AND '2026-10-23 23:59:59'
ORDER BY OrderDate;
GO
решения задач 3.1–3.4 и 3.6
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    SELECT ProductID, Name, Price
    -- → задача 3.1: диапазон цен с включительными границами
    FROM dbo.Products
    WHERE Price BETWEEN 1000 AND 2000
    ORDER BY Price;
    -- → дешёвые сначала
    GO
    
    SELECT OrderID, OrderDate, Status
    -- → задача 3.2: три статуса одним IN, без цепочки OR
    FROM dbo.Orders
    WHERE Status IN (N'New', N'Paid', N'Shipped');
    GO
    
    SELECT ProductID, Name, Attributes
    -- → задача 3.3: подстрока в атрибутах; строки с NULL молча отсекаются LIKE
    FROM dbo.Products
    WHERE Attributes LIKE N'%black%'
    ORDER BY Name;
    GO
    
    SELECT CustomerID, FullName, Email
    -- → задача 3.4: якорь слева от домена, чтобы не поймать лишнее
    FROM dbo.Customers
    WHERE Email LIKE N'shared%@example.com';
    GO
    
    SELECT MAX(OrderDate) AS last_order_at FROM dbo.Orders;
    -- → задача 3.6, первый приём: отдельный запрос узнаёт самую свежую дату
    GO
    
    SELECT OrderID, OrderDate, Status, TotalAmount
    -- → задача 3.6, второй приём: весь найденный день одним диапазоном
    FROM dbo.Orders
    WHERE OrderDate BETWEEN '2026-10-23 00:00:00' AND '2026-10-23 23:59:59'
    ORDER BY OrderDate;
    -- → дата подставлена из предыдущего результата
    GO
    решения задач 3.1–3.4 и 3.6
↑ К оглавлению

6. Типичные ошибки урока

  1. Равно NULL вместо IS NULL — ошибки нет, результат пустой; диагностика счётчиками обеих половин.
  2. Кириллический литерал без N — возможен пустой результат; столбцы базы NVARCHAR, N-литералы пишутся всегда.
  3. Шаблон без якоря — %shared% способен поймать лишнее с той же подстрокой; якорь слева: shared%@example.com.
  4. Потерянный конец дня — BETWEEN до голой даты при DATETIME2 ловит дату лишь на момент полуночи.
  5. NOT при NULL в столбце — строки молча теряются; проверка — приём полноты задачи 3.5.
  6. Работает одно условие из двух — пропущены скобки вокруг OR; ветки гоняются по отдельности и сравниваются числами строк.
↑ К оглавлению

7. Чек-лист практики и источники

Чек-лист урока 3

отметки делаются по факту

Источники для углубления:

  1. Предложение WHERE: синтаксис и примеры;
  2. LIKE: символы шаблона;
  3. IS NULL: проверки на неопределённость.
Итог: предикаты читаются и пишутся уверенно, NULL больше не ловушка, а правило. Урок 4 соединяет таблицы: ключи, INNER JOIN, LEFT JOIN и размножение строк.

Проверено: 23.10.2026

↑ К оглавлению
Конспект:
Четверг, 22 октября 2026