Главная · Блог о тестировании ·
SQL для тестировщика: запросы, проверки базы и задачи с собеседований
SQL полезен тестировщику, когда отвечает на продуктовый вопрос: сохранился ли заказ, создал ли повтор запроса дубль, у каких клиентов нет активности и не остались ли частичные данные после ошибки. Разбираем эти вопросы на одной понятной схеме, а не перечисляем команды без контекста.
- Сформулировать бизнес-инвариант
- Прочитать схему и связи
- Предсказать ожидаемые строки
- Выполнить read-only запрос и сравнить
| Таблица | Важные поля | Бизнес-смысл |
|---|---|---|
| customers | id, email, owner_id, created_at | Клиент CRM и назначенный менеджер |
| orders | id, customer_id, status, total, created_at | Заказ клиента и его текущее состояние |
| payments | id, order_id, external_id, status, amount | Результат попытки оплаты во внешней системе |
| activities | id, customer_id, kind, created_at | Звонок, письмо или встреча в CRM |
1. Прочитай схему до написания SQL
Название таблицы не описывает данные полностью. Уточни первичный ключ, внешние ключи, допустимость NULL, словарь статусов и ограничения уникальности. Без этого правдоподобный запрос может отвечать совсем не на тот вопрос.
В нашей схеме customers.id — ключ клиента; orders.customer_id и activities.customer_id ссылаются на него; payments.order_id ссылается на orders.id. У одного клиента бывает много заказов и активностей, а у заказа — несколько попыток оплаты. Эта кардинальность объясняет, почему JOIN иногда размножает строки.
- Перед JOIN опиши связь словами.
- Отличай отсутствующую строку от строки с NULL в поле.
- Уточняй timezone и правила soft delete для временных и удалённых данных.
Сначала реши самостоятельно
Объясни, почему у одного заказа может быть несколько payments и что сравнить, чтобы доказать двойное списание.
2. SELECT и WHERE: точная проверка одной операции
После ответа 201 от POST /orders не ищи запись только по статусу. Сохрани order id из ответа и запроси именно эту строку. Сравни customer_id, status, total и время создания с запросом и контрактом. Явный список колонок делает доказательство читаемым и не вытаскивает лишние данные.
Временной диапазон помогает, если id недоступен, но он слабее: параллельный тест может создать похожую строку. ORDER BY не фильтрует записи, а LIMIT без однозначной сортировки способен вернуть разные строки в двух запусках.
SELECT id, customer_id, status, total, created_at FROM orders WHERE id = 1842 AND customer_id = 77;
Сначала реши самостоятельно
Напиши запрос оплаченных заказов клиента 77 с 1 октября 2026 года, от новых к старым. До запуска перечисли ожидаемые колонки.
3. JOIN: сравнение связанных бизнес-объектов
INNER JOIN возвращает только пары, найденные с обеих сторон. LEFT JOIN сохраняет каждую строку левой таблицы, а отсутствующую правую часть заполняет NULL. Это главное различие для отрицательных проверок: клиентов без заказов или заказов без платежей.
Фильтр по правой таблице в WHERE может удалить NULL-строки и фактически превратить LEFT JOIN в INNER JOIN. Для вопроса «клиенты без оплаченных заказов» условие paid ставится в ON, после чего проверяется NULL у id присоединённого заказа.
SELECT c.id, c.email FROM customers c LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'paid' WHERE o.id IS NULL ORDER BY c.id;
Сначала реши самостоятельно
Найди заказы, для которых вообще нет строк payments. Определи левую таблицу и id, которое должно быть NULL.
4. NULL, пустая строка и отсутствие данных — разные случаи
NULL означает неизвестное или отсутствующее значение. Он не равен пустой строке и не проверяется через = NULL: нужны IS NULL и IS NOT NULL. В продукте NULL, пустая строка и пробелы могут приходить из разных веток валидации, поэтому объединять их без требования нельзя.
Из-за трёхзначной логики сравнение с NULL даёт unknown и условие WHERE не проходит. COALESCE удобен в отчётах, но преждевременная подстановка значения по умолчанию может замаскировать дефект.
SELECT id, email,
CASE
WHEN email IS NULL THEN 'null'
WHEN TRIM(email) = '' THEN 'blank'
ELSE 'present'
END AS email_state
FROM customers;
Сначала реши самостоятельно
Спроектируй отдельные проверки для NULL, пустой строки и трёх пробелов в customers.email. Назови ожидаемое поведение продукта.
5. COUNT, GROUP BY и HAVING: дубли и неверные суммы
GROUP BY задаёт группы, COUNT, SUM, MIN и MAX считают значения внутри них, HAVING фильтрует готовые группы. WHERE отбирает исходные строки до группировки. Смешение этих этапов — частая ошибка на собеседованиях.
COUNT(*) считает строки, а COUNT(column) пропускает NULL в выбранной колонке. Одновременный JOIN нескольких связей one-to-many размножает строки и завышает суммы. Лучше агрегировать дочерние таблицы заранее, а не лечить результат необъяснимым DISTINCT.
SELECT LOWER(TRIM(email)) AS normalized_email, COUNT(*) AS customer_count FROM customers WHERE email IS NOT NULL GROUP BY LOWER(TRIM(email)) HAVING COUNT(*) > 1; SELECT external_id, COUNT(*) AS attempts, SUM(amount) AS charged FROM payments WHERE status = 'succeeded' GROUP BY external_id HAVING COUNT(*) > 1;
Сначала реши самостоятельно
Верни клиентов с более чем тремя activities в октябре 2026 года: customer id и количество, от большего к меньшему.
6. Проверяй целостность, а не только ответ API
Успешный ответ не доказывает согласованность связанных данных. Заказ может сохраниться без позиций, платёж — ссылаться на несуществующий заказ, а два активных клиента — иметь email, который должен быть уникальным. Foreign key, UNIQUE и CHECK защищают от классов дефектов, но приложение обязано корректно обработать отказ ограничения.
Поиск orphan-строк полезен для диагностики, но не разрешает читать лишние production-данные. Используй синтетические записи, выбирай минимум колонок и прикладывай к багу идентификаторы, а не полные персональные данные.
SELECT p.id, p.order_id FROM payments p LEFT JOIN orders o ON o.id = p.order_id WHERE o.id IS NULL; SELECT id, order_id, amount FROM payments WHERE status = 'succeeded' AND amount <= 0;
Сначала реши самостоятельно
Сформулируй три инварианта orders/payments и по одному read-only запросу, который способен обнаружить нарушение каждого.
7. Транзакции, повторы и конкуренция
Транзакция объединяет изменения: они фиксируются вместе либо откатываются вместе. Проверь сбой между связанными записями: если payment успешен, а обновление order упало, какое состояние останется и как сработает reconciliation? Один успешный прогон не доказывает атомарность.
Параллельные запросы находят дефекты, невидимые последовательно. Две конвертации лида или повтор после timeout могут одновременно пройти проверку «запись уже существует?». Бизнес-инвариант должны удержать unique constraint, idempotency key или согласованная блокировка.
SELECT order_id, external_id, status, amount, created_at FROM payments WHERE order_id = 1842 ORDER BY created_at, id;
Сначала реши самостоятельно
Опиши два параллельных запроса, способных создать двух клиентов из одного CRM-лида. Назови инвариант и ожидаемое evidence в базе.
8. Как решать SQL-задачу на собеседовании
Не начинай печатать до определения результата. Перескажи задачу бизнес-языком, назови сохраняемую таблицу, набросай связи и предскажи одну-две строки. После запроса мысленно проверь ноль, одну и несколько связанных записей.
Ответ Middle дополнительно называет ограничения: дубли исходных данных, границы timezone, soft delete, isolation и допустимость прямого доступа к БД. Просить схему нормально; молча выдумывать колонки и кардинальность хуже одного точного вопроса.
- Объясни каждое условие JOIN.
- Скажи, возможны ли дубли строк результата.
- Проверь пустой набор и несколько дочерних строк.
- Отделяй диагностический SELECT от cleanup-скрипта.
- Не запускай UPDATE или DELETE ради демонстрации уверенности.
Сначала реши самостоятельно
Подготовь пятиминутное объяснение и два запроса. Затем замени условие «без заказов» на «без оплаченных заказов» и объясни изменение в JOIN.
Частые вопросы про SQL для QA
Нужен ли ручному тестировщику сложный SQL?
Большинство задач Junior начинается с безопасных SELECT: фильтры, JOIN, NULL и агрегаты. Сложность зависит от продукта. Связать запрос с бизнес-инвариантом полезнее, чем запомнить сложный синтаксис без контекста.
Можно ли QA выполнять UPDATE и DELETE?
Только в согласованном окружении, с нужными правами и планом очистки. Production-расследование обычно требует read-only доступа. Не импровизируй с разрушающим SQL на собеседовании или инциденте.
Заменяет ли результат из базы проверку API?
Нет. API подтверждает внешний контракт, база даёт evidence о сохранённом состоянии. Сильная проверка сопоставляет оба уровня, когда это оправдано риском, и учитывает изменчивость внутренней схемы.
Какие темы SQL чаще спрашивают у тестировщиков?
Часто встречаются SELECT/WHERE, INNER и LEFT JOIN, NULL, COUNT, GROUP BY, HAVING, поиск дублей, подзапросы и транзакции. Конкретный объём отличается, поэтому уточни у рекрутера СУБД и формат задания.
SQL — не набор волшебных фраз для интервью, а точный способ превратить правило продукта в evidence о данных. Начинай с ожидаемых строк, сохраняй запрос read-only и объясняй, что результат доказывает, а что оставляет неизвестным.