5 ноября 2026

SQL для тестировщика: 7 ключевых тем с задачами с реальных собеседований

Регрессионный тест прошёл, UI говорит «Оплачено» — а в базе данных две записи вместо одной. Деньги списаны дважды. Баг обнаружила поддержка клиентов через неделю. SQL для тестировщика — инструмент, позволяющий заглянуть под поверхность интерфейса: один SELECT, и скрытая ловушка видна.

SQL-код для тестировщика на экране ноутбука с подсветкой синтаксиса

Три сценария, в которых QA-инженер обращается к SQL: верификация данных после операции, подготовка тестовых данных (один INSERT заменяет день ручного заполнения форм), диагностика бага — выяснить, проблема в данных, API или UI. Семь тем ниже закрывают 90% вопросов на QA-собеседовании.

image

Сделайте следующий шаг в карьере

Подберите обучение, которое поможет освоить новые компетенции

Выбрать курс
Тема
Уровень
Частота на собеседованиях
SELECT и WHERE Junior Почти всегда
JOIN Junior / Middle 80% собеседований
Агрегатные функции Junior / Middle Часто
GROUP BY и HAVING Middle Часто
Подзапросы Middle Регулярно
Порядок выполнения SQL Middle Регулярно
NULL-семантика Junior / Middle Часто

Все примеры в статье работают с двумя таблицами: users (id, name, email, age, city, phone) и orders (id, user_id, amount, status, created_at).

Зачем тестировщику SQL: три реальных сценария

Тестирование баз данных — не отдельная дисциплина, а естественная часть работы QA-инженера. Баги в данных существуют независимо от того, видны они через интерфейс или нет.

Сценарий 1: верификация данных. Пользователь нажал «Оплатить», API вернул 200 OK, экран показывает «Оплачен». Но правильно ли записано в таблице payments? Прямой запрос к базе отвечает за секунду:

SELECT * FROM payments WHERE order_id = 1234;

Если записей две — баг двойного списания зафиксирован. Без SQL этот дефект останется незамеченным.

Сценарий 2: подготовка тестовых данных. Вместо регистрации 100 тестовых пользователей через форму весь день — один INSERT:

INSERT INTO users (name, email, age, city)

VALUES (‘Test User 1’, ‘test1@example.com’, 25, ‘Москва’),

(‘Test User 2’, ‘test2@example.com’, 30, ‘СПб’);

Сценарий 3: диагностика бага. Пользователь пишет: «Не вижу свои заказы». Три источника проблемы — UI, API, данные. Один SELECT с JOIN указывает на конкретный слой:

SELECT u.name, o.id AS order_id, o.status

FROM users u

LEFT JOIN orders o ON u.id = o.user_id

WHERE u.email = ‘user@example.com’;

Если запрос возвращает строки с NULL в колонке order_id — проблема в данных: заказы не записаны. Если данные корректны — ищем дальше в API или UI.

SELECT и базовые запросы

SQL (Structured Query Language, язык структурированных запросов) — декларативный язык: вы описываете что хотите получить, а не как это делать. Запросы на выборку данных принято обозначать аббревиатурой DQL (Data Query Language). Они начинаются с ключевого слова SELECT.

SELECT * FROM orders; — все колонки, все строки

SELECT id, amount FROM orders; — только нужные колонки

SELECT DISTINCT city FROM users; — уникальные значения

SELECT * удобен для быстрой проверки. В рабочих запросах лучше указывать конкретные колонки: меньше нагрузки на базу, лучше читаемость.

Сортировка и ограничение выборки — стандартная задача при проверке тарификации:

SELECT id, amount FROM orders

ORDER BY amount DESC

LIMIT 5;

Запрос вернёт пять самых дорогих заказов.

WHERE, фильтрация и операторы

Клауза WHERE фильтрует строки до группировки и агрегации. Основные операторы:

WHERE age > 25

WHERE city IN (‘Москва’, ‘СПб’)

WHERE amount BETWEEN 100 AND 500

WHERE name LIKE ‘An%’

WHERE phone IS NULL

LIKE — инструмент поиска по шаблону строки. Три варианта использования:

Шаблон
Что означает
Пример
% Любое количество символов (включая ноль) LIKE ‘%gmail%’ — email содержит «gmail»
_ Ровно один произвольный символ LIKE ‘_va’ — «Ева», «Ова», «Два»
text% Начинается с «text» LIKE ‘An%’ — «Anna», «Andrei»
%text Заканчивается на «text» LIKE ‘%.ru’ — домены .ru

Критический момент: WHERE phone = NULL не работает. NULL — маркер отсутствия значения, не пустая строка и не ноль. Любое сравнение через = с NULL возвращает NULL, а не TRUE. Правильный вариант — IS NULL или IS NOT NULL.

JOIN — ключевая тема на собеседованиях

JOIN объединяет данные из двух таблиц по условию — как правило, по первичному (PRIMARY KEY) и внешнему (FOREIGN KEY) ключам в реляционной базе данных. Эта тема встречается на 80% QA-собеседований: именно JOIN позволяет находить нарушения целостности данных.

Представьте: в orders есть заказ с user_id = 99, но пользователя с таким id в users нет. «Осиротевшая» запись (orphaned record) — типичный дефект целостности. JOIN помогает его обнаружить:

SELECT o.id, o.user_id

FROM orders o

LEFT JOIN users u ON o.user_id = u.id

WHERE u.id IS NULL;

Шпаргалка по 4 типам JOIN

Тип
Формула
Что возвращает
NULL в результате
INNER JOIN A ∩ B Только совпадающие строки из обеих таблиц Нет
LEFT JOIN A + (A ∩ B) Все строки левой таблицы + совпадения из правой Да, в колонках правой таблицы
RIGHT JOIN B + (A ∩ B) Все строки правой таблицы + совпадения из левой Да, в колонках левой таблицы
FULL JOIN A ∪ B Все строки из обеих таблиц Да, с обеих сторон

Два редких типа, которые спрашивают на Middle: SELF JOIN — таблица объединяется сама с собой через псевдоним (когда строки связаны между собой, например сотрудник и его менеджер в одной таблице employees). CROSS JOIN — декартово произведение: каждая строка первой таблицы комбинируется с каждой строкой второй.

QA-специфика: тестировщики чаще применяют LEFT JOIN, а не INNER — он сохраняет все записи основной таблицы и показывает, где данные отсутствуют.

Задача «Пользователи без заказов»

Классика QA-собеседования: найти всех пользователей, у которых нет ни одного заказа.

SELECT u.id, u.name, u.email

FROM users u

LEFT JOIN orders o ON u.id = o.user_id

WHERE o.id IS NULL;

Почему LEFT, а не INNER JOIN? INNER вернул бы только пользователей с заказами — и мы потеряли бы именно тех, кого ищем. LEFT JOIN сохраняет всех из users, а WHERE o.id IS NULL фильтрует тех, у кого нет соответствия в orders.

QA-контекст: такой запрос проверяет целостность регистрации — все ли зарегистрированные пользователи корректно создаются в системе и могут совершать покупки.

Агрегатные функции: COUNT, SUM, AVG, MAX, MIN

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

Функция
Что считает
Поведение с NULL
Пример
COUNT(*) Все строки таблицы NULL включается 5 строк → COUNT(*) = 5
COUNT(col) Строки с non-NULL значением NULL пропускается 5 строк, 2 NULL → COUNT(phone) = 3
SUM(col) Сумму значений NULL пропускается NULL не влияет на сумму остальных
AVG(col) Среднее значение NULL не учитывается в знаменателе Среднее вычисляется только по non-NULL строкам
MAX / MIN(col) Максимум / минимум NULL пропускается Не участвует в сравнении

Ловушка с COUNT — одна из самых частых на собеседованиях. Если в таблице 5 пользователей, но у двух не заполнен телефон: COUNT(*) вернёт 5, COUNT(phone) — 3. Правильный выбор зависит от задачи. QA должен понимать разницу и делать выбор осознанно.

GROUP BY и HAVING — фильтрация групп

GROUP BY объединяет строки с одинаковыми значениями в группы и применяет агрегатные функции к каждой группе. HAVING фильтрует уже сгруппированные данные — в отличие от WHERE, которая фильтрует строки до группировки.

Последовательность: WHERE убирает ненужные строки → GROUP BY формирует группы → HAVING отбирает нужные группы.

Задача: найти пользователей, потративших более 10 000 ₽.

SELECT u.name, SUM(o.amount) AS total_spent

FROM users u

JOIN orders o ON u.id = o.user_id

GROUP BY u.id, u.name

HAVING SUM(o.amount) > 10000

ORDER BY total_spent DESC;

Почему SUM нельзя в WHERE? Агрегатные функции вычисляются после GROUP BY — это шаг 5 в порядке выполнения SQL, а WHERE — шаг 3. К моменту фильтрации в WHERE группы ещё не созданы, счётчики не вычислены.

Подзапросы в SQL

Подзапрос (вложенный запрос) — это SELECT внутри другого SELECT. Он выполняется первым, результат передаётся во внешний запрос.

Скалярный подзапрос возвращает одно значение. Пример — найти пользователей старше среднего возраста:

SELECT name, age

FROM users

WHERE age > (SELECT AVG(age) FROM users);

Пора сменить профессию? Начните с понятного плана

Подберите программу под ваш опыт, цели и желаемый формат занятости

  • Востребованные навыки без лишней теории.
  • Поддержку экспертов на каждом этапе
  • Помощь с выходом на рынок труда
Подобрать обучение
image

Подзапрос с IN возвращает список значений — пользователи с оплаченными заказами:

SELECT name

FROM users

WHERE id IN (

SELECT DISTINCT user_id FROM orders WHERE status = ‘paid’

);

Два подхода к «заказу с максимальной суммой»:

— Подзапрос: вернёт ВСЕ записи с максимальной суммой

SELECT * FROM orders

WHERE amount = (SELECT MAX(amount) FROM orders);

— ORDER BY + LIMIT: вернёт только ОДНУ запись

SELECT * FROM orders

ORDER BY amount DESC

LIMIT 1;

Если записей с максимальной суммой несколько — подходы дают разный результат. Умение объяснить эту разницу на собеседовании отделяет Junior от Middle.

Порядок выполнения SQL-запроса

Текст запроса читается слева направо, но СУБД (система управления базами данных) выполняет его иначе. Порядок фиксированный, и от его понимания зависит, какие ошибки вы допустите:

FROM(1) → JOIN(2) → WHERE(3) → GROUP BY(4) → HAVING(5) → SELECT(6) → ORDER BY / LIMIT(7)

Три практических следствия:

  1. Алиас из SELECT недоступен в WHERE, GROUP BY и HAVING — эти клаузы выполняются раньше. Если total — алиас из SELECT, то WHERE total > 1000 вызовет ошибку.
  2. Агрегатные функции недоступны в WHERE — потому что GROUP BY ещё не запущен.
  3. Подзапрос во FROM выполняется первым — он становится временной таблицей для внешнего запроса.

Код-ловушка с псевдонимом (алиасом):

— Ошибка: cnt — алиас из SELECT, WHERE не видит его

SELECT user_id, COUNT(*) AS cnt

FROM orders

WHERE cnt > 1 — ❌

GROUP BY user_id;

— Правильно: фильтр сгруппированных данных через HAVING

SELECT user_id, COUNT(*) AS cnt

FROM orders

GROUP BY user_id

HAVING COUNT(*) > 1; — ✅

Порядок выполнения SQL-запроса: схема 7 шагов от FROM до LIMIT

5 ловушек, на которых валятся кандидаты

Знание синтаксиса — одно, понимание семантики SQL — другое. Пять ошибок, которые воспроизводят даже опытные кандидаты:

Ловушка
Неправильно
Правильно
Причина
NULL-с Ловушка Неправильно Правильно
— — — —
NULL-сравнение WHERE city = NULL WHERE city IS NULL NULL — маркер; = NULL возвращает NULL, а не TRUE
COUNT(*) вместо COUNT(col) COUNT(*) для подсчёта непустых значений COUNT(phone) COUNT(*) включает строки с NULL
Не все поля в GROUP BY SELECT name, city, COUNT(*) → GROUP BY city GROUP BY city, name Все не-агрегатные поля должны быть в GROUP BY
WHERE вместо HAVING WHERE COUNT(*) > 1 HAVING COUNT(*) > 1 Агрегаты вычисляются после WHERE
Алиас в WHERE WHERE cnt > 1 (cnt — алиас из SELECT) HAVING COUNT(*) > 1 SELECT выполняется позже WHERE

Ловушка с NULL встречается особенно часто — потому что интуитивно кажется, что = NULL должно работать. Но NULL обозначает «значение неизвестно». Результат любого сравнения с неизвестным — снова неизвестность (NULL), а не FALSE.

Задачи с реальных SQL-собеседований

Задачи ниже взяты из практики QA-собеседований — Junior и Middle уровни. Все решаются на таблицах users и orders, введённых в начале статьи. Задача Junior: написать рабочий запрос. Задача Middle: написать и объяснить выбор каждого инструмента.

Junior-уровень: 5 задач с решениями

Задача
Концепт
SQL-код
Пользователи без заказов LEFT JOIN + IS NULL SELECT u.name FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL
Топ-3 покупателя по сумме JOIN + SUM + ORDER BY + LIMIT SELECT u.name, SUM(o.amount) AS total FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name ORDER BY total DESC LIMIT 3
Дублирующиеся email GROUP BY + HAVING SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1
Второй по величине заказ DISTINCT + OFFSET SELECT DISTINCT amount FROM orders ORDER BY amount DESC LIMIT 1 OFFSET 1
Пользователи старше среднего Скалярный подзапрос SELECT name, age FROM users WHERE age > (SELECT AVG(age) FROM users)

Каждая задача привязана к реальному QA-сценарию: проверка целостности регистрации, верификация тарификации, поиск дублей в пользовательских данных.

Middle-уровень: усложнённые задачи

Задача 1: кто потратил больше 10 000 ₽?

SELECT u.name, SUM(o.amount) AS total_spent

FROM users u

JOIN orders o ON u.id = o.user_id

GROUP BY u.id, u.name

HAVING SUM(o.amount) > 10000

ORDER BY total_spent DESC;

Почему SUM в HAVING, а не в WHERE — разобрано в разделе про порядок выполнения.

Задача 2: конверсия по городам

SELECT

u.city,

COUNT(DISTINCT u.id) AS total_users,

COUNT(DISTINCT o.user_id) AS users_with_orders,

ROUND(COUNT(DISTINCT o.user_id) * 100.0 / COUNT(DISTINCT u.id), 1) AS conversion_pct

FROM users u

LEFT JOIN orders o ON u.id = o.user_id

GROUP BY u.city;

Три нюанса: COUNT(DISTINCT u.id) — уникальные пользователи; 100.0 вместо 100 — для дробного деления; LEFT JOIN — иначе потеряем города без заказов. Middle-кандидат объясняет каждый элемент, а не просто воспроизводит код.

Где изучить SQL тестировщику: курсы и ресурсы

Ресурс
Формат
Уровень
Цена
Дифференциатор
Тестировщик программного обеспечения (ProfiFuture) Онлайн, 7 недель С нуля 29 900 ₽ со скидкой SQL в составе полной программы QA; господдержка — 75% стоимости покрывается образовательной квотой
SQLBolt Интерактивные уроки в браузере Начинающий→средний Бесплатно 15 минут в день, без установки ПО
LeetCode (SQL) Практические задачи Easy→Hard Базовый уровень — бесплатно Задачи с реальных технических собеседований
w3resource Упражнения с проверкой Любой Бесплатно Мгновенная проверка решения онлайн

Практический совет: установите PostgreSQL локально, создайте таблицы users и orders с тестовыми данными и решайте задачи руками. Прочитанный SQL усваивается в разы хуже написанного.

Часто задаваемые вопросы

Обязательно ли тестировщику знать SQL?

Да. QA-инженер без SQL видит только вершину айсберга: баги с дублированием записей или нарушением целостности данных невидимы через интерфейс. SQL позволяет обратиться к базе напрямую. Для Junior достаточно SELECT + WHERE + JOIN + IS NULL; для Middle — GROUP BY, подзапросы и понимание порядка выполнения.

Что означает аббревиатура SQL?

SQL расшифровывается как Structured Query Language — язык структурированных запросов. Создан IBM в 1974 году (Дональд Чемберлин и Раймонд Бойс), стандартизирован ANSI/ISO в 1986 году. Декларативный язык для работы с реляционными базами данных: выборка, вставка, обновление и удаление данных.

Что такое синтаксис SQL и почему важен порядок клауз?

Синтаксис SQL — правила написания запросов. SELECT идёт первым в тексте, но выполняется шестым. Нарушение логического порядка клауз (например, HAVING без GROUP BY) вызывает ошибку. СУБД нечувствительна к регистру ключевых слов, но строго соблюдает порядок: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY / LIMIT.

В чём разница COUNT(*) и COUNT(column)?

COUNT() считает все строки таблицы, включая строки с NULL. COUNT(column) считает только строки, где значение column не равно NULL. Пример: таблица с 5 пользователями, у 2 не указан телефон — COUNT() = 5, COUNT(phone) = 3. Выбор зависит от задачи; на собеседованиях это ловушка №2.

Почему WHERE city = NULL не работает в SQL?

NULL — маркер отсутствия значения, не пустая строка и не ноль. Любое сравнение через «=» с NULL возвращает NULL, а не TRUE. Правильный оператор: IS NULL или IS NOT NULL. Это ловушка №1 на QA-собеседованиях — ошибку допускают даже кандидаты с опытом.

Чем отличается WHERE от HAVING в SQL?

WHERE фильтрует строки до группировки и не может содержать агрегатные функции. HAVING фильтрует уже сгруппированные данные после GROUP BY и работает с COUNT, SUM и другими агрегатами. Порядок выполнения: WHERE(шаг 3) → GROUP BY(шаг 4) → HAVING(шаг 5).

Что такое порядок выполнения SQL-запроса?

Фиксированная последовательность: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY / LIMIT. Поэтому алиас из SELECT нельзя использовать в WHERE или GROUP BY — эти клаузы выполняются раньше. Алиас доступен только в ORDER BY. Агрегаты вычисляются в HAVING, а не в WHERE.

Как тестировщику подготовиться к SQL-собеседованию за неделю?

Для Junior: SELECT + WHERE + JOIN + IS NULL + ORDER BY / LIMIT на таблицах users и orders. Решить 5 задач: пользователи без заказов, дубли email, топ покупателей, второй по величине заказ, подзапрос. Для Middle: добавить GROUP BY + HAVING + порядок выполнения + объяснение логики выбора инструментов. JOIN спрашивают в 80% собеседований — начинать именно с него.

Превратите интерес к теме в новую профессию

Оставьте заявку — поможем выбрать программу и расскажем об условиях обучения

Оставить заявку
icon