WHERE SQL — 100 универсальных примеров от простого к сложному

🟢 WHERE SQL. Введение

WHERE — оператор фильтрации строк в SQL. Именно он сужает набор данных перед агрегированием, сортировкой и соединениями.
По запросу where sql обычно ищут способы строить точные условия: равенство/неравенство, диапазоны, проверка на NULL, поиск по подстроке, фильтрация по датам, а также использование подзапросов с EXISTS/IN и логики AND/OR/NOT.

Ниже — 100 унифицированных примеров для всех реализаций SQL.

Синтаксис

SELECT
    столбцы
FROM
    источник
WHERE
    условие_фильтрации;

Типовая конструкция

SELECT
    o.order_id,
    o.customer_id,
    o.total_amount
FROM
    orders AS o
WHERE
    o.status       = 'PAID'
    AND o.order_date >= DATE '2025-01-01'
ORDER BY
    o.total_amount DESC;

Базовые условия: равенство и сравнения

Фильтр оплаченных заказов по статусу:

SELECT
    o.order_id,
    o.status
FROM
    orders AS o
WHERE
    o.status = 'PAID';

Цена выше порога — числовое сравнение:

SELECT
    p.product_id,
    p.price
FROM
    products AS p
WHERE
    p.price > 100;

Рейтинг не ниже 8 — граничное условие:

SELECT
    f.film_id,
    f.rating
FROM
    films AS f
WHERE
    f.rating >= 8;

Исключаем тестовые записи — неравенство:

SELECT
    u.user_id,
    u.email
FROM
    users AS u
WHERE
    u.is_test <> 1;

Курс валюты равен USD — точное совпадение:

SELECT
    a.account_no,
    a.currency
FROM
    accounts AS a
WHERE
    a.currency = 'USD';

Складской остаток меньше минимума — сигнал пополнения:

SELECT
    s.product_id,
    s.qty_on_hand
FROM
    stock AS s
WHERE
    s.qty_on_hand < 10;

Баланс неотрицательный — проверка нижней границы:

SELECT
    a.account_no,
    a.balance
FROM
    account_balances AS a
WHERE
    a.balance >= 0;

Статус доставки из набора готовых значений через IN:

SELECT
    d.delivery_id,
    d.status
FROM
    deliveries AS d
WHERE
    d.status IN ('SHIPPED','DELIVERED');

Сумма операции в диапазоне через BETWEEN:

SELECT
    tx.tx_id,
    tx.amount
FROM
    transactions AS tx
WHERE
    tx.amount BETWEEN 100 AND 500;

Исключаем нулевые продажи — явное сравнение:

SELECT
    o.order_id,
    o.total_amount
FROM
    orders AS o
WHERE
    o.total_amount > 0;

Комбинации условий: AND, OR, NOT

Оплачено и сумма больше 100 — двойной фильтр:

SELECT
    o.order_id,
    o.total_amount
FROM
    orders AS o
WHERE
    o.status = 'PAID'
    AND o.total_amount > 100;

Активные пользователи из выбранной страны или города:

SELECT
    u.user_id,
    u.country,
    u.city
FROM
    users AS u
WHERE
    u.is_active = 1
    AND (u.country = 'DE' OR u.city = 'Berlin');

Не архив и не удалено — отрицание признаков:

SELECT
    p.post_id,
    p.title
FROM
    posts AS p
WHERE
    NOT p.is_archived = 1
    AND NOT p.is_deleted = 1;

Отбор категорий кроме скрытых — NOT с IN:

SELECT
    c.category_id,
    c.category
FROM
    categories AS c
WHERE
    c.category NOT IN ('Hidden','Internal');

Цены вне диапазона — комбинированное условие:

SELECT
    p.product_id,
    p.price
FROM
    products AS p
WHERE
    p.price < 10 OR p.price > 1000;

Срочные или просроченные задачи:

SELECT
    t.task_id,
    t.priority,
    t.due_date
FROM
    tasks AS t
WHERE
    t.priority = 'HIGH'
    OR t.due_date   < CURRENT_DATE;

Клиенты без VIP и без блокировок:

SELECT
    c.customer_id,
    c.is_vip,
    c.is_blocked
FROM
    customers AS c
WHERE
    c.is_vip    = 0
    AND c.is_blocked = 0;

Фильтр по нескольким брендам и активному флагу:

SELECT
    p.product_id,
    p.brand
FROM
    products AS p
WHERE
    p.brand IN ('A','B','C')
    AND p.is_active = 1;

Логическое ветвление по статусу и типу:

SELECT
    t.ticket_id,
    t.status,
    t.type
FROM
    tickets AS t
WHERE
    (t.status = 'OPEN' AND t.type = 'INCIDENT')
    OR (t.status = 'PENDING' AND t.type = 'REQUEST');

Отрицание шаблона имени файла — NOT с LIKE:

SELECT
    f.file_id,
    f.file_name
FROM
    files AS f
WHERE
    f.file_name NOT LIKE 'tmp_%';

Поиск по подстроке: LIKE и шаблоны

Email на домене example.com — правый шаблон:

SELECT
    u.user_id,
    u.email
FROM
    users AS u
WHERE
    u.email LIKE '%@example.com';

Артикулы, начинающиеся с ABC — левый шаблон:

SELECT
    p.product_id,
    p.sku
FROM
    products AS p
WHERE
    p.sku LIKE 'ABC%';

Поиск слов внутри заголовка (оба края):

SELECT
    b.book_id,
    b.title
FROM
    books AS b
WHERE
    b.title LIKE '%data%';

Регистронезависимый поиск псевдонимом — нормализация:

SELECT
    a.author_id,
    a.nickname
FROM
    authors AS a
WHERE
    LOWER(a.nickname) LIKE '%dev%';

Фильтр по телефону без масок — только цифры:

SELECT
    c.contact_id,
    c.phone
FROM
    contacts AS c
WHERE
    REGEXP_REPLACE(c.phone, '[^0-9]', '') LIKE '7%';

Поиск пути файлов по расширению:

SELECT
    f.file_id,
    f.path
FROM
    files AS f
WHERE
    f.path LIKE '%.csv';

Товары с пометкой PRO в названии:

SELECT
    p.product_id,
    p.title
FROM
    products AS p
WHERE
    p.title LIKE '% PRO %';

Имена с дефисом в середине — маска:

SELECT
    u.user_id,
    u.full_name
FROM
    users AS u
WHERE
    u.full_name LIKE '%-%';

Адреса, содержащие улицу Ленина:

SELECT
    a.address_id,
    a.full_address
FROM
    addresses AS a
WHERE
    a.full_address LIKE '%Ленина%';

Фильтрация тегов по префиксу:

SELECT
    t.tag_id,
    t.tag
FROM
    tags AS t
WHERE
    t.tag LIKE 'sql_%';

Проверки на NULL: IS NULL / IS NOT NULL

Почта отсутствует — отбор незаполненных адресов:

SELECT
    u.user_id,
    u.email
FROM
    users AS u
WHERE
    u.email IS NULL;

Только заполненные телефоны — исключаем NULL:

SELECT
    c.contact_id,
    c.phone
FROM
    contacts AS c
WHERE
    c.phone IS NOT NULL;

Дата доставки ещё не назначена:

SELECT
    o.order_id,
    o.delivery_date
FROM
    orders AS o
WHERE
    o.delivery_date IS NULL;

Сотрудники с указанным менеджером:

SELECT
    e.emp_id,
    e.manager_id
FROM
    employees AS e
WHERE
    e.manager_id IS NOT NULL;

Оценка отсутствует — выявляем пробелы в данных:

SELECT
    r.student_id,
    r.score
FROM
    results AS r
WHERE
    r.score IS NULL;

Исключить пустые категории и нули после нормализации:

SELECT
    p.product_id,
    p.category
FROM
    products AS p
WHERE
    NULLIF(TRIM(p.category), '') IS NOT NULL;

Последний логин не зафиксирован — поиск проблемных аккаунтов:

SELECT
    u.user_id,
    u.last_login_at
FROM
    users AS u
WHERE
    u.last_login_at IS NULL;

Комнаты без привязки к курсам:

SELECT
    r.room_id,
    r.room_no
FROM
    rooms AS r
WHERE
    r.course_id IS NULL;

Номер страховки заполнен — фильтр обязательных полей:

SELECT
    pt.patient_id,
    pt.policy_no
FROM
    patients AS pt
WHERE
    pt.policy_no IS NOT NULL;

Отключаем аномалии: отрицательные цены и NULL:

SELECT
    p.product_id,
    p.price
FROM
    products AS p
WHERE
    p.price IS NOT NULL
    AND p.price >= 0;

Фильтрация по датам и времени

Заказы за текущий месяц — нижняя граница:

SELECT
    o.order_id,
    o.order_date
FROM
    orders AS o
WHERE
    o.order_date >= DATE_TRUNC('month', CURRENT_DATE);

Визиты за последние 7 дней:

SELECT
    v.visit_id,
    v.visit_date
FROM
    visits AS v
WHERE
    v.visit_date >= CURRENT_DATE - INTERVAL '7' DAY;

Платежи за 2024 год — точные рамки:

SELECT
    p.payment_id,
    p.paid_at
FROM
    payments AS p
WHERE
    p.paid_at >= DATE '2024-01-01'
    AND p.paid_at <  DATE '2025-01-01';

Сеансы длительнее 5 минут:

SELECT
    s.session_id,
    s.duration_min
FROM
    sessions AS s
WHERE
    s.duration_min > 5;

События до контрольной даты:

SELECT
    e.event_id,
    e.created_at
FROM
    events AS e
WHERE
    e.created_at < DATE '2025-06-01';

Инциденты, закрытые сегодня:

SELECT
    i.incident_id,
    i.closed_at
FROM
    incidents AS i
WHERE
    i.closed_at >= CURRENT_DATE
    AND i.closed_at <  CURRENT_DATE + INTERVAL '1' DAY;

Уведомления, отправленные ночью (0–6):

SELECT
    n.notification_id,
    n.sent_at
FROM
    notifications AS n
WHERE
    EXTRACT(HOUR FROM n.sent_at) BETWEEN 0 AND 6;

Доставки, назначенные на выходные:

SELECT
    d.delivery_id,
    d.scheduled_at
FROM
    deliveries AS d
WHERE
    EXTRACT(DOW FROM d.scheduled_at) IN (0,6);

Посты, опубликованные в прошлом квартале:

SELECT
    p.post_id,
    p.published_on
FROM
    posts AS p
WHERE
    p.published_on >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '1' QUARTER
    AND p.published_on <  DATE_TRUNC('quarter', CURRENT_DATE);

Покупки, совершённые вечером (18–23):

SELECT
    o.order_id,
    o.order_time
FROM
    orders AS o
WHERE
    EXTRACT(HOUR FROM o.order_time) BETWEEN 18 AND 23;

Подзапросы: IN, EXISTS, корреляции

Клиенты, делавшие заказы — фильтр через IN:

SELECT
    c.customer_id,
    c.full_name
FROM
    customers AS c
WHERE
    c.customer_id IN (
        SELECT
            o.customer_id
        FROM
            orders AS o
    );

Статьи, у которых есть комментарии — EXISTS:

SELECT
    p.post_id,
    p.title
FROM
    posts AS p
WHERE
    EXISTS (
        SELECT
            1
        FROM
            comments AS c
        WHERE
            c.post_id = p.post_id
    );

Товары, которых нет в поставках — NOT EXISTS:

SELECT
    p.product_id,
    p.title
FROM
    products AS p
WHERE
    NOT EXISTS (
        SELECT
            1
        FROM
            supply AS s
        WHERE
            s.product_id = p.product_id
    );

Фильмы со средним рейтингом > 8 — коррелированный подзапрос:

SELECT
    f.film_id,
    f.title
FROM
    films AS f
WHERE
    (
        SELECT
            AVG(r.rating)
        FROM
            reviews AS r
        WHERE
            r.film_id = f.film_id
    ) > 8;

Курсы с ≥ 10 записавшихся — вложенный подсчёт:

SELECT
    c.course_id,
    c.title
FROM
    courses AS c
WHERE
    (
        SELECT
            COUNT(*)
        FROM
            enrollments AS e
        WHERE
            e.course_id = c.course_id
    ) >= 10;

Сотрудники, у которых есть задачи в статусе OPEN:

SELECT
    e.emp_id,
    e.full_name
FROM
    employees AS e
WHERE
    EXISTS (
        SELECT
            1
        FROM
            tasks AS t
        WHERE
            t.owner_id = e.emp_id
            AND t.status  = 'OPEN'
    );

Аккаунты без транзакций за 30 дней:

SELECT
    a.account_no
FROM
    accounts AS a
WHERE
    NOT EXISTS (
        SELECT
            1
        FROM
            transactions AS tx
        WHERE
            tx.account_no = a.account_no
            AND tx.tx_date >= CURRENT_DATE - INTERVAL '30' DAY
    );

Пассажиры с билетами более чем у 2 авиакомпаний:

SELECT
    p.passenger_id,
    p.full_name
FROM
    passengers AS p
WHERE
    (
        SELECT
            COUNT(DISTINCT t.airline)
        FROM
            tickets AS t
        WHERE
            t.passenger_id = p.passenger_id
    ) > 2;

Полисы, по которым были выплаты:

SELECT
    pl.policy_id,
    pl.plan_code
FROM
    policies AS pl
WHERE
    EXISTS (
        SELECT
            1
        FROM
            payouts AS po
        WHERE
            po.policy_id = pl.policy_id
    );

Проекты с исполнителями из разных отделов:

SELECT
    pr.project_id,
    pr.title
FROM
    projects AS pr
WHERE
    (
        SELECT
            COUNT(DISTINCT e.dept_id)
        FROM
            assignments AS a
            INNER JOIN employees AS e
                ON e.emp_id = a.emp_id
        WHERE
            a.project_id = pr.project_id
    ) >= 2;

Фильтрация на фоне соединений: JOIN

Заказы магазина в выбранном городе:

SELECT
    o.order_id,
    s.city
FROM
    orders AS o
    INNER JOIN stores AS s
        ON s.store_id = o.store_id
WHERE
    s.city = 'Paris';

Отзывы только по активным фильмам:

SELECT
    r.review_id,
    f.title
FROM
    reviews AS r
    INNER JOIN films AS f
        ON f.film_id = r.film_id
WHERE
    f.is_active = 1;

Товары с остатком на конкретном складе:

SELECT
    p.product_id,
    s.qty_on_hand
FROM
    products AS p
    INNER JOIN stock AS s
        ON s.product_id   = p.product_id
WHERE
    s.warehouse_id = 1;

Сотрудники с задачами в статусе IN_PROGRESS:

SELECT
    e.emp_id,
    t.task_id
FROM
    employees AS e
    INNER JOIN tasks AS t
        ON t.owner_id = e.emp_id
WHERE
    t.status = 'IN_PROGRESS';

Клиенты с оплатами по карте:

SELECT
    c.customer_id,
    p.payment_id
FROM
    customers AS c
    INNER JOIN payments AS p
        ON p.customer_id = c.customer_id
WHERE
    p.method = 'CARD';

Доставки с задержкой более суток:

SELECT
    d.delivery_id,
    o.order_id
FROM
    deliveries AS d
    INNER JOIN orders AS o
        ON o.order_id = d.order_id
WHERE
    d.delivered_at - d.shipped_at > INTERVAL '1' DAY;

Ученики, записанные на конкретный курс:

SELECT
    s.student_id,
    c.title
FROM
    students AS s
    INNER JOIN enrollments AS e
        ON e.student_id = s.student_id
    INNER JOIN courses AS c
        ON c.course_id = e.course_id
WHERE
    c.course_code = 'SQL101';

Маршруты с активными рейсами сегодня:

SELECT
    r.route_code,
    f.flight_no
FROM
    routes AS r
    INNER JOIN flights AS f
        ON f.route_id = r.route_id
WHERE
    f.flight_date = CURRENT_DATE;

Менеджеры с закрытыми сделками за месяц:

SELECT
    u.user_id,
    d.deal_id
FROM
    users AS u
    INNER JOIN deals AS d
        ON d.owner_id = u.user_id
WHERE
    d.closed_on >= DATE_TRUNC('month', CURRENT_DATE);

Устройства с ошибками последней телеметрии:

SELECT
    d.device_id,
    t.state
FROM
    devices AS d
    INNER JOIN telemetry AS t
        ON t.device_id = d.device_id
WHERE
    t.state = 'ERR';

Где фильтровать: до GROUP BY и после через HAVING

Исключить отменённые до подсчёта заказов:

SELECT
    o.store_id,
    COUNT(*) AS orders_cnt
FROM
    orders AS o
WHERE
    o.status <> 'CANCELLED'
GROUP BY
    o.store_id;

Фильтр по диапазону дат до агрегации:

SELECT
    o.order_date,
    SUM(o.total_amount) AS day_sum
FROM
    orders AS o
WHERE
    o.order_date >= CURRENT_DATE - INTERVAL '30' DAY
GROUP BY
    o.order_date;

Сначала отбросить неактивные товары, затем агрегировать:

SELECT
    p.category,
    AVG(p.price) AS avg_price
FROM
    products AS p
WHERE
    p.is_active = 1
GROUP BY
    p.category;

Отбор по месту — затем группировка:

SELECT
    s.city,
    COUNT(*) AS stores_cnt
FROM
    stores AS s
WHERE
    s.country = 'FR'
GROUP BY
    s.city;

Фильтр нулевых сумм перед GROUP BY:

SELECT
    a.account_no,
    COUNT(*) AS tx_cnt
FROM
    transactions AS t
    INNER JOIN accounts AS a
        ON a.account_no = t.account_no
WHERE
    t.amount > 0
GROUP BY
    a.account_no;

Группировка по дню — только оплаченные:

SELECT
    o.order_date,
    SUM(o.total_amount) AS day_sum
FROM
    orders AS o
WHERE
    o.status = 'PAID'
GROUP BY
    o.order_date;

Сравнение: отбор по сумме уже через HAVING:

SELECT
    s.store_id,
    SUM(o.total_amount) AS revenue
FROM
    orders AS o
    INNER JOIN stores AS s
        ON s.store_id = o.store_id
GROUP BY
    s.store_id
HAVING
    SUM(o.total_amount) > 10000;

Индикатор: только успешные логины в день:

SELECT
    l.login_date,
    COUNT(*) AS ok_logins
FROM
    logins AS l
WHERE
    l.is_success = 1
GROUP BY
    l.login_date;

Фильтр по валюте до свёртки:

SELECT
    o.currency,
    SUM(o.total_amount) AS sum_amount
FROM
    orders AS o
WHERE
    o.currency = 'EUR'
GROUP BY
    o.currency;

Смешанный подход: часть условий до, часть — после:

SELECT
    c.customer_id,
    SUM(o.total_amount) AS total_amount
FROM
    customers AS c
    LEFT JOIN orders AS o
        ON o.customer_id = c.customer_id
WHERE
    o.status = 'PAID'
GROUP BY
    c.customer_id
HAVING
    SUM(o.total_amount) >= 1000;

Практика оптимизации условий

Диапазоны вместо функций над колонкой — SARGable:

SELECT
    o.order_id,
    o.order_date
FROM
    orders AS o
WHERE
    o.order_date >= DATE '2025-01-01'
    AND o.order_date <  DATE '2025-02-01';

Нормализация регистра до LIKE — вычисление слева:

SELECT
    u.user_id,
    u.email
FROM
    users AS u
WHERE
    LOWER(u.email) LIKE '%@example.com';

Избегаем ведущих подстановок, когда возможно:

SELECT
    p.product_id,
    p.title
FROM
    products AS p
WHERE
    p.title LIKE 'SQL%';

Используем IN вместо больших цепочек OR:

SELECT
    s.store_id,
    s.city
FROM
    stores AS s
WHERE
    s.city IN ('Paris','Lyon','Marseille');

NULL‑безопасные проверки через IS NOT NULL:

SELECT
    c.customer_id,
    c.phone
FROM
    customers AS c
WHERE
    c.phone IS NOT NULL;

Оптимальные границы времени через BETWEEN:

SELECT
    n.notification_id,
    n.sent_at
FROM
    notifications AS n
WHERE
    n.sent_at BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1' DAY;

Улучшение селективности фильтром по флагу:

SELECT
    p.product_id,
    p.is_active
FROM
    products AS p
WHERE
    p.is_active = 1;

Сначала узкий фильтр, затем соединение:

SELECT
    o.order_id,
    o.store_id
FROM
    orders AS o
WHERE
    o.order_date >= CURRENT_DATE - INTERVAL '7' DAY;

Композитные ключи — фильтр по обоим полям:

SELECT
    s.store_id,
    s.dept_id
FROM
    store_departments AS s
WHERE
    s.store_id = 10
    AND s.dept_id  = 5;

Балансная проверка: исключаем нулевые и NULL одновременно:

SELECT
    a.account_no,
    a.balance
FROM
    account_balances AS a
WHERE
    a.balance IS NOT NULL
    AND a.balance <> 0;

Прикладные сценарии

E‑commerce: скидочные продажи по диапазону процента:

SELECT
    o.order_id,
    oi.product_id,
    oi.discount_pct
FROM
    orders AS o
    INNER JOIN order_items AS oi
        ON oi.order_id = o.order_id
WHERE
    oi.discount_pct BETWEEN 10 AND 50;

Финансы: подозрительные транзакции по сумме и типу:

SELECT
    tx.tx_id,
    tx.amount,
    tx.kind
FROM
    transactions AS tx
WHERE
    tx.amount >= 10000
    AND tx.kind   = 'CASH';

HR: кандидаты с опытом и знанием SQL:

SELECT
    cand.candidate_id,
    cand.full_name
FROM
    candidates AS cand
WHERE
    cand.years_exp >= 3
    AND LOWER(cand.summary) LIKE '%sql%';

Медицина: визиты без диагноза, но с оплатой:

SELECT
    v.visit_id,
    v.paid_amount
FROM
    visits AS v
WHERE
    v.diagnosis_code IS NULL
    AND v.paid_amount   > 0;

Образование: экзамены с оценкой ниже проходного:

SELECT
    r.student_id,
    r.score
FROM
    results AS r
WHERE
    r.score < 60;

IoT: датчики в состоянии ошибки за последние сутки:

SELECT
    t.sensor_id,
    t.state,
    t.collected_at
FROM
    telemetry AS t
WHERE
    t.state = 'ERR'
    AND t.collected_at >= CURRENT_DATE - INTERVAL '1' DAY;

Логистика: доставки с превышением SLA:

SELECT
    d.delivery_id,
    d.delivered_at,
    d.promised_at
FROM
    deliveries AS d
WHERE
    d.delivered_at > d.promised_at;

Безопасность: IP с ≥ 10 неуспешными логинами:

SELECT
    l.ip_address,
    COUNT(*) AS failed_cnt
FROM
    logins AS l
WHERE
    l.is_success = 0
GROUP BY
    l.ip_address
HAVING
    COUNT(*) >= 10;

CRM: сделки без владельца или с пустым статусом:

SELECT
    d.deal_id,
    d.status,
    d.owner_id
FROM
    deals AS d
WHERE
    (d.owner_id IS NULL OR d.status IS NULL);

Retail: товары в дефиците по складу:

SELECT
    s.product_id,
    s.warehouse_id,
    s.qty_on_hand
FROM
    stock AS s
WHERE
    s.qty_on_hand < 5;

Заключение

where sql — это ядро точной выборки данных. Стройте читаемые условия, используйте логические связки, обрабатывайте NULL явно, применяйте подзапросы с EXISTS/IN, фильтруйте до GROUP BY и оставляйте агрегатные ограничения для HAVING.

Соблюдайте выравнивание, поддерживайте индексацию и избегайте ненужных функций над колонками — это ускорит запросы и упростит поддержку.


 

Понравилась статья? Поделиться с друзьями: