Главная · Блог о тестировании ·

SQL для тестировщика: запросы, проверки базы и задачи с собеседований

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

Повторяемый алгоритм проверки базы данных
  1. Сформулировать бизнес-инвариант
  2. Прочитать схему и связи
  3. Предсказать ожидаемые строки
  4. Выполнить read-only запрос и сравнить
Учебная схема, которая используется во всех примерах
Таблица Важные поля Бизнес-смысл
customersid, email, owner_id, created_atКлиент CRM и назначенный менеджер
ordersid, customer_id, status, total, created_atЗаказ клиента и его текущее состояние
paymentsid, order_id, external_id, status, amountРезультат попытки оплаты во внешней системе
activitiesid, 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 без однозначной сортировки способен вернуть разные строки в двух запусках.

Проверка заказа, созданного через API
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 удобен в отчётах, но преждевременная подстановка значения по умолчанию может замаскировать дефект.

Три состояния отсутствующего email
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.

Поиск дублей email и платёжного идентификатора
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-данные. Используй синтетические записи, выбирай минимум колонок и прикладывай к багу идентификаторы, а не полные персональные данные.

Orphan payments и некорректная успешная сумма
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 или согласованная блокировка.

Evidence для повтора, который не должен списать дважды
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 и объясняй, что результат доказывает, а что оставляет неизвестным.

Запросить бесплатный доступ к практике QA Следующий разбор

← Все разборы