Українська версія

SQL і бази даних: питання для QA співбесіди

Практичні SQL та database питання українською для QA й automation engineers: queries, joins, aggregation, transactions, data integrity, BI validation і тестові сценарії.
49питань
ETL, warehouse & BIMiddleCommonTheory

Що має покривати наскрізне тестування ETL- або ELT-пайплайна?

Відповідь

Перевіряйте вилучення даних із джерела, обробку схеми й типів, правила трансформації, джойни та фільтри, завантаження в цільове сховище та ідемпотентні повторні запуски. Звіряйте кількості та контрольні суми на межах етапів, тестуйте запізнілі, дубльовані, відсутні й пошкоджені записи, а також перевіряйте lineage (простежуваність походження даних), свіжість даних, спостережуваність і відновлення. Захищайте чутливі дані на всьому шляху тестування.

Сильна відповідь включає

  • покриває межі extract, transform і load
  • використовує звірку та «ворожі» (adversarial) записи
  • тестує повторні запуски, відновлення та захист даних

Практичні приклади

Звірити source і target control totals
SELECT 'source' AS dataset, COUNT(*) AS rows, SUM(amount) AS total_amount
FROM source_orders
WHERE load_date = DATE '2026-08-21'

UNION ALL

SELECT 'target' AS dataset, COUNT(*) AS rows, SUM(amount) AS total_amount
FROM warehouse_orders
WHERE load_date = DATE '2026-08-21';

Починайте ETL validation з незалежних control totals на кожній межі, а при розбіжності counts або amounts переходьте до row-level diff. Окремо перевіряйте rejected rows, type conversions, joins, defaults та idempotency повторного запуску, а не лише status job.

Очікуваний результат: Source і target totals збігаються з урахуванням задокументованих rejects і transformations.

#etl#elt#pipeline#reconciliation

Джерела

Develop and test Dataflow pipelinesGoogle Cloud · Unit, integration and end-to-end testing for batch and streaming pipelinesAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contractsGreat Expectations CoreGreat Expectations · Data-quality expectations, validation definitions and evidence
ETL, warehouse & BIJuniorCommonTheory

Які виміри якості даних (data-quality dimensions) має оцінювати тестувальник?

Відповідь

Поширені виміри включають повноту, валідність, точність, узгодженість, унікальність, своєчасність чи свіжість та референтну цілісність. Визначайте кожен вимір як вимірюване правило, прив'язане до бізнес-рішення, знаходьте авторитетне джерело даних і прийнятний поріг, а потім моніторте результати за джерелом і часом, а не звітуйте однією змішаною оцінкою якості.

Сильна відповідь включає

  • називає кілька окремих вимірів
  • перетворює виміри на вимірювані бізнес-правила
  • визначає авторитетне джерело даних і пороги

Практичні приклади

Перетворити data-quality dimensions на вимірювані SQL checks
SELECT
  COUNT(*) AS total_rows,
  SUM(CASE WHEN customer_id IS NULL THEN 1 ELSE 0 END) AS missing_customer_id,
  SUM(CASE WHEN amount < 0 THEN 1 ELSE 0 END) AS invalid_amount,
  COUNT(DISTINCT customer_id) AS distinct_customers,
  MAX(updated_at) AS newest_update
FROM orders;

SELECT LOWER(TRIM(email)) AS normalized_email, COUNT(*) AS copies
FROM users
WHERE email IS NOT NULL
GROUP BY LOWER(TRIM(email))
HAVING COUNT(*) > 1;

Completeness, validity, uniqueness і freshness потребують явних rules та thresholds. Цей example перевіряє fields, які реально існують у sample orders schema, і застосовує той самий normalized-email uniqueness rule, що й duplicate example.

Очікуваний результат: Окремі quality metrics, які можна порівняти з явними acceptance thresholds.

#data-quality#completeness#validity#freshness

Джерела

Great Expectations CoreGreat Expectations · Data-quality expectations, validation definitions and evidenceAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contracts
NoSQL & data modelsJuniorCommonTheory

Чим відрізняються реляційні та нереляційні бази даних і що це змінює в підході до тестування?

Відповідь

Реляційні бази даних організовують дані в таблиці з заданими зв'язками, обмеженнями (constraints) та транзакційною семантикою, тоді як нереляційні системи використовують інші моделі — документи, пари ключ-значення, широкі колонки або графи. Цей вибір впливає на патерни запитів, гарантії узгодженості даних, суворість схеми та поведінку при масштабуванні. Тестувальник має перевіряти реальну модель даних продукту, правила цілісності, гарантії при паралельному доступі, відновлення після збоїв і підтримувані шляхи запитів, а не припускати, що будь-яка база поводиться як SQL.

Сильна відповідь включає

  • порівнює моделі даних і гарантії узгодженості
  • пов'язує архітектурні рішення з обсягом тестування
  • не трактує NoSQL як єдину однорідну технологію

Практичні приклади

Реляційний запит через заданий зв'язок
SELECT o.id, o.total, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'paid';

Foreign key задає явний шлях JOIN. У document database дані клієнта можуть бути вкладені в order, тому еквівалентний тест більше перевіряє дубльовані embedded values та їх узгоджене оновлення, а не коректність JOIN.

Очікуваний результат: Оплачені замовлення, зіставлені з існуючим рядком клієнта.

#database#sql#nosql#data-model

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryDOU — QA interview: 250+ questionsDOU · Coverage signal across Junior, Middle, Senior, AQA, web, mobile and practical topics
SQL fundamentals & CRUDJuniorCommonTheory

Що роблять команди SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, COMMIT і ROLLBACK?

Відповідь

SELECT читає дані; INSERT, UPDATE і DELETE змінюють рядки; а CREATE, ALTER і DROP визначають або змінюють об'єкти бази даних. COMMIT робить успішну роботу транзакції незворотною (durable), а ROLLBACK скасовує її незафіксовані зміни. Тестувальник має розуміти, які команди можуть змінювати стан, обмежувати доступ на запис у спільних середовищах, використовувати точні умови WHERE та перевіряти як кількість зачеплених рядків, так і кінцевий бізнес-стан.

Сильна відповідь включає

  • розділяє читання даних, зміну даних, зміну схеми та керування транзакціями
  • усвідомлює ризик деструктивних команд
  • перевіряє кількість зачеплених рядків і кінцевий стан

Практичні приклади

Перевірити write перед зміною та зберегти rollback
BEGIN;

SELECT id, status
FROM orders
WHERE customer_id = 1;

UPDATE orders
SET status = 'cancelled'
WHERE customer_id = 1;

-- Inspect affected rows before deciding:
SELECT id, status
FROM orders
WHERE customer_id = 1;

ROLLBACK; -- use COMMIT only when the change is intended

SELECT використовується як безпечний preview, UPDATE змінює стан, а явна транзакція залишає операцію оборотною під час перевірки. Та сама логіка стосується INSERT/DELETE, тоді як CREATE/ALTER/DROP змінюють об'єкти схеми.

Очікуваний результат: Рядки змінюються всередині транзакції та повертаються до попереднього стану після ROLLBACK.

#sql#crud#ddl#transactions

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
DB design & integrityJuniorCommonTheory

Чим відрізняються первинний ключ, зовнішній ключ та обмеження UNIQUE, NOT NULL, CHECK і DEFAULT?

Відповідь

Primary key унікально ідентифікує кожен row. Foreign key вимагає, щоб non-NULL referencing value відповідав існуючому referenced key; чи дозволений NULL, визначає column definition. UNIQUE забороняє дублікати згідно з NULL rules конкретної СУБД, NOT NULL вимагає value, CHECK перевіряє predicate, а DEFAULT підставляє value, якщо його не передано. Тести мають покривати valid writes, кожне порушення, composite keys, update/delete actions і product-specific NULL behavior.

Сильна відповідь включає

  • пояснює ідентичність рядків і referential integrity (цілісність зв'язків)
  • розрізняє всі основні типи обмежень
  • тестує негативні сценарії запису та поведінку при оновленні/видаленні

Практичні приклади

Оголосити та перевірити constraints цілісності
CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  age INTEGER CHECK (age >= 18),
  status TEXT DEFAULT 'active'
);

CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL REFERENCES users(id)
);

-- Negative tests:
INSERT INTO users (id, email, age) VALUES (1, 'a@example.com', 17);
INSERT INTO orders (id, user_id) VALUES (10, 999);

Перший негативний INSERT має порушити CHECK, другий — foreign key. Аналогічно потрібно перевірити duplicate email, NULL email, UPDATE та поведінку DELETE для referenced users.

Очікуваний результат: Обидва негативні INSERT відхиляються базою даних.

#database#sql#constraints#keys

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
SQL fundamentals & CRUDJuniorCommonTheory

Чому NULL потребує особливої обробки в SQL-запитах і тестах?

Відповідь

NULL означає невідоме або відсутнє значення, тому звичайні порівняння з ним зазвичай дають UNKNOWN, а не TRUE чи FALSE. Замість оператора рівності слід використовувати IS NULL або IS NOT NULL, і варто пам'ятати, що NULL по-різному впливає на JOIN, агрегатні функції, NOT IN, сортування та унікальність залежно від конкретної СУБД. Відсутні значення потрібно тестувати явно на кожній межі — вводі, зберіганні, запиті, обчисленні, експорті та в UI, — оскільки непомітна обробка NULL може змінити і кількість рядків, і бізнес-підсумки.

Сильна відповідь включає

  • розуміє трійкову (three-valued) логіку SQL
  • використовує IS NULL, а не оператор рівності
  • охоплює JOIN, агрегатні функції та наскрізне поширення NULL

Практичні приклади

Порівняння NULL: неправильна та правильна форми
-- Wrong: comparison evaluates to UNKNOWN
SELECT * FROM users WHERE phone = NULL;

-- Correct
SELECT * FROM users WHERE phone IS NULL;

-- Safer anti-join when the subquery key may contain NULL
SELECT c.*
FROM customers c
WHERE NOT EXISTS (
  SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

NULL не є звичайним значенням, тому equality не перевіряє відсутність. NOT EXISTS також безпечніший за NOT IN, коли підзапит може містити NULL, бо three-valued logic інакше може зробити всі порівняння UNKNOWN.

Очікуваний результат: IS NULL повертає відсутні phone, а NOT EXISTS — клієнтів без пов'язаних orders.

#sql#database#null#three-valued-logic

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Aggregation & window functionsJuniorCommonPractical

Коли в SQL-запиті слід використовувати WHERE, GROUP BY та HAVING?

Відповідь

WHERE фільтрує окремі рядки до групування, GROUP BY формує групи для агрегатних функцій, а HAVING фільтрує вже отримані групи. Наприклад, WHERE може обмежити замовлення одним роком, GROUP BY customer — порахувати підсумки, а HAVING SUM(amount) більше за поріг — залишити лише клієнтів із високим сумарним чеком. Надійний тест перевіряє граничні значення, NULL, дубльовані рядки після JOIN, рівень деталізації групування (grain) та незалежно порахований контрольний підсумок.

Сильна відповідь включає

  • розміщує фільтрацію рядків до агрегації
  • використовує HAVING для умов на агрегати
  • перевіряє рівень деталізації групування та ефект дублювання рядків

Практичні приклади

Відфільтрувати рядки, агрегувати та відфільтрувати групи
SELECT customer_id, SUM(amount) AS total
FROM orders
WHERE order_date >= DATE '2025-01-01'
  AND order_date <  DATE '2026-01-01'
GROUP BY customer_id
HAVING SUM(amount) > 1000
ORDER BY total DESC;

WHERE відкидає рядки до агрегації, GROUP BY створює одну групу на клієнта, а HAVING залишає лише агреговані групи, total яких перевищує поріг.

Очікуваний результат: Клієнти, сума замовлень яких за 2025 рік перевищує 1000.

#sql#group-by#having#aggregation

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Joins, subqueries & CTEsJuniorCommonTheory

У чому різниця між UNION та UNION ALL і як би ви тестували результат кожного з них?

Відповідь

UNION об'єднує сумісні набори результатів і видаляє дублікати рядків, тоді як UNION ALL зберігає кожен рядок і зазвичай уникає витрат на видалення дублікатів. Обидві частини запиту повинні повертати сумісну кількість і типи колонок, але з назвами колонок та порядком рядків усе одно потрібно працювати свідомо. Тести мають охоплювати дублікати всередині та між джерелами, значення NULL, порожні вхідні набори, перетворення типів, сортування, застосоване лише до фінального результату, та звірку очікуваної кількості рядків.

Сильна відповідь включає

  • розрізняє видалення дублікатів і їх збереження
  • згадує сумісність форми результатів (кількість і типи колонок)
  • тестує NULL, порожні вхідні дані та звірку кількості рядків

Практичні приклади

Побачити видалення та збереження дублікатів
-- Removes identical result rows
SELECT email FROM active_customers
UNION
SELECT email FROM archived_customers;

-- Keeps every row
SELECT email FROM active_customers
UNION ALL
SELECT email FROM archived_customers;

UNION видаляє дублікати в об'єднаному результаті. UNION ALL зберігає multiplicity і зазвичай дешевший, коли видалення дублікатів не є вимогою.

Очікуваний результат: UNION може повернути менше рядків за UNION ALL, якщо input sets перетинаються.

#sql#union#duplicates#query-results

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
SQL fundamentals & CRUDJuniorCommonPractical

Як би ви використали SQL, щоб знайти дублікати бізнес-ключів у таблиці?

Відповідь

Згрупуйте рядки за колонками бізнес-ключа та відфільтруйте групи через HAVING COUNT(*) більше одиниці, наприклад SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1. Спершу варто визначити, чи регістр, пробіли, NULL та історичні версії мають вважатися однаковим ключем. Потім слід переглянути самі рядки, підтвердити реальне правило унікальності й не видаляти нічого, поки не з'ясовані відповідальність (ownership), політика зберігання та можливість відновлення.

Сильна відповідь включає

  • використовує GROUP BY разом з HAVING COUNT
  • уточнює, що насправді є бізнес-ключем
  • відокремлює виявлення дублікатів від деструктивного очищення

Практичні приклади

Знайти дублікати нормалізованого бізнес-ключа
SELECT
  LOWER(TRIM(email)) AS normalized_email,
  COUNT(*) AS copies
FROM users
WHERE email IS NOT NULL
GROUP BY LOWER(TRIM(email))
HAVING COUNT(*) > 1
ORDER BY copies DESC;

GROUP BY згортає рядки до grain бізнес-ключа, а HAVING фільтрує після обчислення count. Нормалізація відповідає правилу, де регістр і пробіли на краях не мають значення.

Очікуваний результат: Один рядок на кожен дубльований normalized email із кількістю копій.

#sql#database#duplicates#data-quality

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contracts
Joins, subqueries & CTEsJuniorCommonPractical

Як би ви знайшли "осиротілі" (orphan) записи або відсутні зв'язки за допомогою SQL?

Відповідь

Використайте LEFT JOIN від дочірньої таблиці до батьківської та відфільтруйте рядки, де ключ батьківської таблиці дорівнює NULL, або застосуйте NOT EXISTS із корельованим підзапитом. Спершу перевірте, чи є nullable зовнішній ключ допустимим станом, перш ніж вважати кожен відсутній зв'язок дефектом. Порівняйте результат запиту з оголошеними правилами зовнішніх ключів, м'яким видаленням (soft delete), датами набуття чинності та порядком завантаження даних, щоб легітимний тимчасовий стан не був помилково класифікований як порушення цілісності.

Сильна відповідь включає

  • коректно використовує LEFT JOIN або NOT EXISTS
  • розрізняє допустимі NULL-зв'язки та справжніх "сиріт"
  • перевіряє обмеження, м'яке видалення та часові фактори

Практичні приклади

Знайти child rows без існуючого parent
SELECT o.*
FROM orders o
WHERE o.customer_id IS NOT NULL
  AND NOT EXISTS (
    SELECT 1
    FROM customers c
    WHERE c.id = o.customer_id
  );

Child row залишається лише тоді, коли matching parent не існує. Явна умова customer_id IS NOT NULL не дозволяє помилково трактувати навмисно nullable relationship як orphan.

Очікуваний результат: Нуль рядків, коли referential integrity не порушена.

#sql#database#referential-integrity#joins

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contracts
DB design & integrityMiddleCommonTheory

Яку проблему вирішує нормалізація бази даних і коли виправдана денормалізація?

Відповідь

Нормалізація розділяє факти, щоб зменшити дублювання та аномалії при оновленні, вставці й видаленні; перша, друга й третя нормальні форми послідовно обмежують повторювані групи, часткові залежності та транзитивні залежності. Денормалізація свідомо дублює або попередньо обчислює дані, щоб спростити читання або підвищити виміряну продуктивність. Тестувальники мають перевіряти джерело істини (source of truth), узгодженість запису, шляхи міграції й виправлення, поведінку при паралельному доступі та чи справді доказова продуктивність виправдовує додатковий ризик для цілісності даних.

Сильна відповідь включає

  • пов'язує нормалізацію з аномаліями даних
  • розглядає денормалізацію як компроміс, що має спиратися на докази
  • тестує узгодженість і шляхи виправлення даних

Практичні приклади

Нормалізована модель order/customer
SELECT
  o.id AS order_id,
  o.total,
  c.id AS customer_id,
  c.email
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.id = :order_id;

Customer identity зберігається один раз і referenced з orders, що зменшує update anomalies. Denormalized design із копією email у кожному order потребував би тестів узгодженості цих копій після зміни source value.

Очікуваний результат: Один order, зв'язаний з актуальним customer record через relationship.

#database#sql#normalization#data-model

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Joins, subqueries & CTEsMiddleCommonTheory

Коли варто використати підзапит (subquery) або спільний табличний вираз (CTE) замість ще одного JOIN?

Відповідь

Підзапит може виражати перевірку існування, скалярний пошук або належність до множини, а спільний табличний вираз (CTE) дає іменований проміжний результат, що робить складний запит легшим для читання й тестування. JOIN часто зрозуміліший при об'єднанні пов'язаних наборів рядків, але еквівалентний синтаксис не гарантує однаковий план виконання на будь-якій СУБД. Перевіряйте порожні та багаторядкові випадки, поведінку NULL в IN чи NOT IN, ефект дублювання рядків, завершення рекурсії та реальний план виконання для запитів, чутливих до продуктивності.

Сильна відповідь включає

  • обирає синтаксис виходячи з семантики та зрозумілості
  • розпізнає пастки з NULL та кардинальністю
  • не припускає, що еквівалентний синтаксис дає однакову продуктивність

Практичні приклади

CTE для іменованого проміжного результату
WITH customer_totals AS (
  SELECT customer_id, SUM(amount) AS total
  FROM orders
  WHERE status = 'paid'
  GROUP BY customer_id
)
SELECT c.id, c.email, t.total
FROM customers c
JOIN customer_totals t ON t.customer_id = c.id
WHERE t.total > 150;

CTE ізолює агрегацію та дає їй ім'я до JOIN з customer data. Це часто полегшує аналіз і окреме тестування складної логіки порівняно з глибоко вкладеними subqueries.

Очікуваний результат: Customers у sample database, paid-order total яких перевищує 150.

#sql#cte#subquery#joins

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Aggregation & window functionsMiddleCommonPractical

Що таке віконні функції (window functions) SQL і як би ви отримали останній рядок або топове значення в кожній групі?

Відповідь

Віконна функція обчислює значення на основі пов'язаних рядків, зберігаючи при цьому кожен окремий рядок, за допомогою конструкції OVER з опціональними PARTITION BY, ORDER BY та правилами вікна (frame). Поширений патерн — присвоїти ROW_NUMBER() OVER (PARTITION BY group_key ORDER BY timestamp DESC) і залишити лише рядки з номером один, щоб отримати останній рядок у кожній групі. Тести мають охоплювати нічиї (ties), однакові timestamp, порядок сортування NULL, детерміноване вторинне сортування, порожні групи та межі вікна для наростаючих чи ковзних обчислень.

Сильна відповідь включає

  • розрізняє віконні функції та агрегацію, що згортає рядки
  • використовує партиціювання та детерміноване сортування
  • тестує нічиї, порядок сортування NULL та межі вікна

Практичні приклади

Останній рядок кожного клієнта через ROW_NUMBER
WITH ranked AS (
  SELECT
    o.*,
    ROW_NUMBER() OVER (
      PARTITION BY customer_id
      ORDER BY created_at DESC, id DESC
    ) AS rn
  FROM orders o
)
SELECT *
FROM ranked
WHERE rn = 1;

Window functions обчислюють значення по пов'язаних рядках, не згортаючи їх. PARTITION BY починає ranking заново для кожного customer, а стабільне вторинне сортування детерміновано розв'язує ties.

Очікуваний результат: Рівно один найновіший order для кожного клієнта, який має orders.

#sql#window-functions#row-number#analytics

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryUnderstand star schema and its importance for Power BIMicrosoft · Facts, dimensions, grain, relationships and semantic-model quality
ETL, warehouse & BIMiddleCommonTheory

Як влаштовані таблиці фактів, таблиці вимірів (dimensions) і рівень деталізації (grain) у зірковій схемі (star schema)?

Відповідь

Таблиця фактів зберігає вимірювані події на заданому рівні деталізації (grain), а таблиці вимірів надають описовий контекст, наприклад клієнта, товар чи дату. Тести мають довести, що кожен рядок факту представляє рівно одну подію на цьому рівні деталізації і що зв'язки з вимірами не дублюють і не втрачають показники. Звіряйте кількості й підсумки від джерела до моделі, охоплюйте невідомі та ті, що надходять із запізненням, виміри (late-arriving dimensions), дати набуття чинності, повільно змінювані атрибути (slowly changing dimensions) та поведінку фільтрів по кожному зв'язку.

Сильна відповідь включає

  • визначає факт, вимір і рівень деталізації (grain)
  • виявляє ефект дублювання рядків та відсутні зв'язки
  • звіряє показники та повільно змінювані атрибути

Практичні приклади

Агрегувати facts через dimensions на заданому grain
SELECT
  d.calendar_date,
  p.category,
  SUM(f.sales_amount) AS revenue
FROM fact_sales f
JOIN dim_date d    ON d.date_key = f.date_key
JOIN dim_product p ON p.product_key = f.product_key
GROUP BY d.calendar_date, p.category
ORDER BY d.calendar_date, p.category;

Fact table містить measures і foreign keys, dimensions — описові attributes. QA має перевірити, що кожен JOIN зберігає очікуваний fact grain і не дублює sales rows.

Очікуваний результат: Один агрегований рядок на date/category без дублювання fact measures.

#database#sql#star-schema#bi

Джерела

Understand star schema and its importance for Power BIMicrosoft · Facts, dimensions, grain, relationships and semantic-model qualityPostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
ETL, warehouse & BIMiddleCommonPractical

Як би ви звірили значення на BI-дашборді з даними в базі-джерелі?

Відповідь

Почніть із бізнес-визначення показника, що відображається, контексту фільтрів, часового поясу, часу оновлення та рівня агрегації, а потім простежте його походження (lineage) через семантичну модель і трансформації до достовірних вихідних рядків. Відтворіть обчислення незалежним SQL-запитом і контрольними підсумками, а не копіюючи логіку самого дашборда. Тестуйте деталізацію (drill-down), приховані фільтри, зв'язки many-to-many, округлення, дані, що надходять із запізненням, захист на рівні рядків (row-level security), затримку кешу чи оновлення та експорт даних, щоб збіг одного підсумкового числа не приховував неузгодженості в деталях.

Сильна відповідь включає

  • простежує бізнес-визначення показника та його походження (lineage)
  • використовує незалежний SQL-запит і контрольні підсумки
  • охоплює фільтри, безпеку, актуальність даних та узгодженість на рівні деталей

Практичні приклади

Незалежний control total для dashboard KPI
SELECT
  COUNT(*) AS paid_orders,
  SUM(amount) AS paid_revenue
FROM orders
WHERE status = 'paid'
  AND paid_at >= TIMESTAMP '2026-08-01 00:00:00'
  AND paid_at <  TIMESTAMP '2026-09-01 00:00:00';

Запит відтворює business definition безпосередньо з source rows, а не копіює dashboard calculation. Порівняння має використовувати ті самі timezone, status definition, refresh cutoff і grain, що й специфікація KPI.

Очікуваний результат: Source counts і totals, які можна звірити з відображеним KPI.

#database#sql#bi#reconciliation

Джерела

Understand star schema and its importance for Power BIMicrosoft · Facts, dimensions, grain, relationships and semantic-model qualityAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contracts
Transactions & concurrencyMiddleCommonTheory

Що означають транзакції бази даних та властивості ACID для тестування?

Відповідь

Транзакція об'єднує операції в одну одиницю, яка або фіксується повністю, або повністю відкочується, а ACID описує атомарність (atomicity), узгодженість (consistency), ізольованість (isolation) та стійкість (durability). Тестуйте успішний сценарій, очікуване відхилення та збій між кожним значущим кроком, щоб довести відсутність часткового бізнес-стану, що залишився. Також перевіряйте поведінку при повторних спробах, дубльовані запити, видимість для паралельних сесій, обмеження, які перевіряються в момент COMMIT, та стійкість стану після перепідключення чи відновлення.

Сильна відповідь включає

  • пояснює всі чотири властивості ACID
  • імітує збої між кроками транзакції
  • охоплює паралельний доступ, повторні спроби та стійкість результату

Практичні приклади

Атомарна багатокрокова бізнес-зміна
BEGIN;

UPDATE inventory
SET quantity = quantity - 1
WHERE product_id = 10
  AND quantity > 0;

-- The application/test must assert that exactly one inventory row changed.
-- If zero rows changed, ROLLBACK and do not create the order.

INSERT INTO orders (id, product_id, status)
VALUES (1001, 10, 'created');

COMMIT;

Atomicity не дозволяє partial commit, але ACID сам по собі не доводить business rule. Caller має перевірити, що guarded inventory update справді успішний, перш ніж створювати order, і зробити rollback, якщо invariant не виконаний.

Очікуваний результат: Order створюється лише коли guarded inventory update успішний; інакше вся transaction rollback.

#database#sql#transactions#acid

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Transactions & concurrencySeniorCommonTheory

Які аномалії паралельного доступу (concurrency anomalies) покликані запобігати рівні ізоляції транзакцій?

Відповідь

Isolation levels спрямовані на anomalies на кшталт dirty read, non-repeatable read, phantom read та lost-update/write-skew patterns; точні гарантії залежать від СУБД. Serialization failure зазвичай є реакцією СУБД, коли execution не може відповідати requested serializable semantics, а не ще одним read anomaly. Тестуйте через контрольовані concurrent sessions, barriers, retries та assertions на business invariant.

Сильна відповідь включає

  • називає конкретні аномалії паралельного доступу
  • використовує детерміновані тести з кількома сесіями
  • перевіряє гарантії конкретної СУБД та обробку повторних спроб

Практичні приклади

Двосесійний експеримент з non-repeatable read
-- Session A
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
-- pause here
SELECT balance FROM accounts WHERE id = 1;
COMMIT;

-- Session B, during the pause
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
COMMIT;

Виконайте statements із контрольованою паузою між reads у Session A. Те, що бачить Session A, залежить від isolation level та MVCC/locking реалізації БД, тому тест має перевіряти business invariant, а не лише назву рівня ізоляції.

Очікуваний результат: Спостереження залежить від isolation level; тест фіксує, чи може другий read побачити committed change із Session B.

#database#transactions#isolation#concurrency

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Transactions & concurrencySeniorCommonTroubleshooting

Як би ви тестували та діагностували блокування (locks), очікування (blocking) і взаємні блокування (deadlocks) у базі даних?

Відповідь

Створіть контрольовані паралельні транзакції, що захоплюють ті самі ресурси у відомому порядку, а потім вимірюйте очікування, тайм-аут, скасування та поведінку відкату. Взаємне блокування (deadlock) утворює цикл сесій, що чекають одна на одну, тому база даних зазвичай перериває одного з учасників, а застосунок повинен безпечно обробити цю відмову. Перевіряйте активні сесії, подання блокувань (lock views) та логи запитів; переконайтеся в послідовному порядку захоплення блокувань, короткій тривалості транзакцій, обмеженому часі очікування, безпечних повторних спробах та відсутності дубльованого бізнес-ефекту.

Сильна відповідь включає

  • розрізняє звичайне очікування та цикл взаємного блокування
  • використовує контрольовані паралельні сесії та діагностику блокувань
  • перевіряє відкат, тайм-аут та ідемпотентність повторних спроб

Практичні приклади

Створити детермінований deadlock у двох sessions
-- Session A
BEGIN;
UPDATE accounts SET balance = balance - 10 WHERE id = 1;
-- then wait and update id = 2
UPDATE accounts SET balance = balance + 10 WHERE id = 2;

-- Session B
BEGIN;
UPDATE accounts SET balance = balance - 20 WHERE id = 2;
-- then wait and update id = 1
UPDATE accounts SET balance = balance + 20 WHERE id = 1;

Кожна session утримує один row lock і потім запитує row, заблокований іншою session, утворюючи цикл очікування. БД має abort одну транзакцію; application test після цього перевіряє rollback, bounded retry та відсутність дубльованого business effect.

Очікуваний результат: Одна транзакція обирається deadlock victim і rollback, замість нескінченного очікування обох.

#database#locking#deadlocks#concurrency

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Indexes & query performanceMiddleCommonTheory

Що покращує індекс бази даних і які витрати чи ризики він приносить?

Відповідь

Індекс — це додаткова структура, яка може знаходити або впорядковувати вибрані рядки швидше, ніж повне сканування таблиці. Він займає місце на диску та додає роботи операціям INSERT, UPDATE і DELETE, а оптимізатор може цілком правомірно проігнорувати його, якщо запит повертає значну частину таблиці. Тестуйте на репрезентативному обсязі та розподілі даних, реальних предикатах запитів, сортуванні, вартості запису, вимогах унікальності, застарілій статистиці та виміряному плані виконання, а не просто перевіряйте факт існування індексу.

Сильна відповідь включає

  • зважує вигоду для читання проти витрат на запис і зберігання
  • розуміє, що оптимізатор може обрати повне сканування замість індексу
  • використовує репрезентативні дані та виміряні плани виконання

Практичні приклади

Проіндексувати реальний query shape та виміряти
CREATE INDEX idx_orders_customer_created
ON orders (customer_id, created_at DESC);

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, status, created_at
FROM orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;

Index відповідає equality predicate та потрібному ordering. Execution plan показує, чи optimizer реально його використовує і чи виправдовує measured read benefit витрати на storage та writes.

Очікуваний результат: Plan, який ефективно дістає найновіші rows клієнта на representative data.

#database#sql#indexes#performance

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryGoogle SRE booksGoogle · Reliability, SLOs, monitoring, incidents and resilience
Indexes & query performanceSeniorCommonTroubleshooting

Як би ви використали EXPLAIN або план виконання, щоб дослідити повільний SQL-запит?

Відповідь

Почніть із плану без виконання, щоб оглянути типи сканування, порядок JOIN, оцінену кількість рядків, фільтри, сортування та кроки агрегації, а потім використовуйте режим аналізу з фактичним виконанням лише там, де це безпечно. Порівнюйте оцінену та фактичну кількість рядків і час, оскільки велика розбіжність може вказувати на застарілу статистику, перекіс даних (skew) або предикат, який оптимізатор не може добре оцінити. Відтворюйте ситуацію на обсязі й параметрах, наближених до продакшену, вимірюйте наскрізну затримку та I/O, і ніколи не сприймайте абстрактну вартість плану як реальні мілісекунди.

Сильна відповідь включає

  • читає типи сканування, JOIN, оцінки та фільтри
  • безпечно застосовує аналіз із реальним виконанням
  • порівнює оцінені та фактичні рядки на реальному навантаженні

Практичні приклади

Порівняти estimated та actual execution
EXPLAIN (ANALYZE, BUFFERS)
SELECT c.id, SUM(o.amount) AS total
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.created_at >= DATE '2026-01-01'
GROUP BY c.id
HAVING SUM(o.amount) > 1000;

Перевіряйте scan types, join order, estimated проти actual rows, filters, sorts, timing і buffer activity. Великі помилки оцінки можуть вказувати на stale statistics, skewed data або predicates, які optimizer погано оцінює.

Очікуваний результат: Measured plan, дорогі або неправильно оцінені nodes якого показують напрям подальшого investigation.

#sql#database#explain#query-plan

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Indexes & query performanceSeniorCommonTheory

Чому порядок колонок має значення в складеному (composite) індексі бази даних?

Відповідь

Багатоколонковий індекс упорядкований за послідовністю своїх ключів, тому предикати та сортування, які використовують перші (leading) колонки, зазвичай отримують найбільшу вигоду, а умова лише на пізнішій колонці може не використовувати індекс ефективно. На результат впливають селективність, предикати діапазону, предикати рівності, потрібний порядок сортування та специфічна для конкретної СУБД поведінка оптимізатора. Перевіряйте кілька реалістичних форм запитів на репрезентативних розподілах даних і планах виконання, а також вимірюйте додаткові витрати на запис, перш ніж додавати індекси, що перекриваються.

Сильна відповідь включає

  • розуміє роль першої (leading) колонки
  • враховує предикати, сортування та селективність
  • підтверджує планами виконання та вимірюванням витрат на запис

Практичні приклади

Поведінка leading columns у composite index
CREATE INDEX idx_orders_customer_status_created
ON orders (customer_id, status, created_at DESC);

-- Strong match for leading columns
SELECT * FROM orders
WHERE customer_id = 1 AND status = 'paid'
ORDER BY created_at DESC;

-- Later column alone may not benefit as efficiently
SELECT * FROM orders
WHERE status = 'paid';

Index впорядкований спочатку за customer_id, потім status і created_at. Queries, що обмежують leading key sequence, зазвичай отримують більше користі, ніж query з filter лише по пізнішій колонці.

Очікуваний результат: Різні plans для двох query shapes, перевірені на representative data, а не припущені лише через наявність index.

#database#sql#composite-index#performance

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
DB design & integritySeniorCommonTest design

Як би ви тестували міграцію схеми бази даних із наявними продакшн-даними?

Відповідь

Тестуйте міграцію на репрезентативній, знеособленій копії даних, що включає старі версії, великі таблиці, реальні граничні випадки з історії та реалістичні індекси й обмеження. Перевіряйте пряму міграцію, сумісність застосунку під час викочування, перетворені значення, кількість рядків і контрольні підсумки, блокування й тривалість, перезапуск після переривання, стратегію відкату чи виправлення вперед (rollback / forward-fix) та резервні копії. Відрепетируйте точний порядок розгортання та доведіть, що змішані версії застосунку не можуть пошкодити дані під час поетапного (rolling) релізу.

Сильна відповідь включає

  • використовує репрезентативні історичні дані та обсяг
  • охоплює сумісність, переривання та відновлення
  • репетирує поведінку розгортання зі змішаними версіями

Практичні приклади

Backfill та перевірка нової constrained column
ALTER TABLE orders ADD COLUMN currency CHAR(3);

UPDATE orders
SET currency = 'USD'
WHERE currency IS NULL;

SELECT COUNT(*) AS missing_currency
FROM orders
WHERE currency IS NULL;

ALTER TABLE orders ALTER COLUMN currency SET NOT NULL;

Безпечна migration розділяє додавання schema, data backfill, verification та enforcement constraint. Production rehearsal також має вимірювати locks/duration і покривати interruption, rollback або forward-fix behavior.

Очікуваний результат: Verification query повертає нуль до того, як застосовується NOT NULL.

#database#sql#schema-migration#deployment

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryGoogle SRE booksGoogle · Reliability, SLOs, monitoring, incidents and resilience
SQL fundamentals & CRUDMiddleCommonTest design

Які межі типів даних у базі даних найчастіше спричиняють дефекти?

Відповідь

До групи високого ризику належать переповнення цілих чисел, точність і округлення дробових чисел, порівняння чисел з плаваючою комою, довжина й кодування тексту, регістр і сортування (collation), дати, часові пояси, переходи на літній/зимовий час, бінарні дані та NULL. Генеруйте значення одразу всередині та за межами кожного заданого ліміту й відстежуйте перетворення на рівнях API, застосунку, бази даних, експорту та звітів. Перевіряйте правила відхилення чи округлення, точність зберігання, поведінку сортування й порівняння та те, що міграції не звужують наявні значення непомітно.

Сильна відповідь включає

  • охоплює числові, текстові та часові межі
  • відстежує перетворення на різних рівнях системи
  • перевіряє точність зберігання та непомітне звуження при міграції

Практичні приклади

Перевірити numeric, text та timestamp boundaries
CREATE TABLE payments (
  amount NUMERIC(10,2) NOT NULL,
  reference VARCHAR(8) NOT NULL,
  paid_at TIMESTAMPTZ NOT NULL
);

-- Boundary probes
INSERT INTO payments VALUES (99999999.99, 'ABCDEFGH', '2026-03-29T00:30:00+00');
INSERT INTO payments VALUES (100000000.00, 'ABCDEFGHI', '2026-03-29T00:30:00+00');

Перший row знаходиться на declared numeric/text limits, другий перевищує обидві. Boundary tests також мають простежувати rounding, encoding і timezone conversions через API, application, database та export layers.

Очікуваний результат: In-range INSERT успішний, а out-of-range values відхиляються замість silent truncation.

#database#sql#data-types#boundaries

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Transactions & concurrencyMiddleCommonPractical

Як забезпечити ізольованість, повторюваність і безпечність інтеграційних тестів для бази даних?

Відповідь

Надайте кожному тесту контрольовані дані та володіння ними через транзакцію, окремого орендаря (tenant) чи схему, детерміновану фікстуру або одноразову базу даних — залежно від того, що саме тест повинен спостерігати. Прибирайте за собою надійно, але не приховуйте поведінку COMMIT, тригерів, блокувань чи міжтранзакційну взаємодію, огортаючи кожен тест у відкат, якого продакшн ніколи не використовує. Паралельні прогони потребують ідентифікаторів, стійких до колізій, обмежених прав доступу, відсутності продакшн-облікових даних, реалістичних обмежень (constraints) та діагностики, що вказує на створені записи, якщо очищення не вдалося.

Сильна відповідь включає

  • обирає спосіб ізоляції, що зберігає поведінку, яка перевіряється
  • обробляє колізії даних при паралельних прогонах і очищення
  • застосовує принцип найменших привілеїв і ніколи не використовує продакшн-облікові дані

Практичні приклади

Integration-test data у межах транзакції
BEGIN;

INSERT INTO users (id, email)
VALUES (900001, 'test-900001@example.invalid');

SELECT id, email
FROM users
WHERE id = 900001;

ROLLBACK;

SELECT COUNT(*)
FROM users
WHERE id = 900001;

Тест володіє унікальним row і робить rollback після assertions. Патерн підходить, коли behavior under test не потребує бачити committed transaction з іншого connection; інакше disposable schema/database або explicit cleanup може бути точнішим.

Очікуваний результат: Після rollback фінальний count дорівнює нулю.

#database#integration-testing#test-data#isolation

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryPlaywright best practicesMicrosoft · Reliable modern browser automation
SQL fundamentals & CRUDLeadCommonScenario

Як би ви тестували реплікацію бази даних, відновлення з резервної копії та перемикання на резерв (failover), не покладаючись лише на індикатори стану?

Відповідь

Записуйте ідентифіковані записи та перевіряйте їхні значення, порядок і видимість на репліках при звичайному навантаженні, затримці (lag), розриві мережі та failover. Відновлюйте резервні копії в ізольованому середовищі та доводьте коректність схеми, обмежень, кількості рядків, критичних бізнес-підсумків, прав доступу й відновлення на певний момент часу (point-in-time recovery) відповідно до заданих цілей відновлення (RPO/RTO). Перевіряйте перепідключення клієнтів, очікування read-after-write, дубльовані чи втрачені записи, захист від split-brain та failback, вимірюючи фактичну втрату даних і час відновлення, а не лише перевіряючи зелені індикатори на дашборді.

Сильна відповідь включає

  • перевіряє самі дані, а не лише стан інфраструктури
  • виконує реальні тестові відновлення в ізольованому середовищі
  • вимірює цілі відновлення (RPO/RTO) та поведінку клієнтів

Практичні приклади

Перевірити restored або replicated business data
SELECT
  COUNT(*) AS order_count,
  SUM(amount) AS revenue,
  MAX(updated_at) AS newest_update
FROM orders
WHERE created_at >= DATE '2026-08-01';

SELECT status, COUNT(*)
FROM orders
WHERE created_at >= DATE '2026-08-01'
GROUP BY status
ORDER BY status;

Запустіть однакові control queries проти authoritative database та replica/restored database. Green infrastructure status недостатній: потрібно також підтвердити critical row counts, totals, recent records, constraints і permissions.

Очікуваний результат: Control totals збігаються в межах явно дозволеного replication/recovery window.

#database#replication#backup#recovery

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryGoogle SRE booksGoogle · Reliability, SLOs, monitoring, incidents and resilience
SQL fundamentals & CRUDMiddleCommonSecurity

Як параметризовані запити запобігають SQL-ін'єкціям і що все одно потрібно тестувати?

Відповідь

Параметризовані запити передають структуру SQL окремо від значень даних, тому ворожий вхід трактується як значення, а не як виконуваний синтаксис. Вони автоматично не роблять безпечними динамічно обрані ідентифікатори, умови сортування, збережені процедури, надмірні привілеї бази даних чи небезпечне формування запитів. Тестуйте кожен шлях вводу даних, включно з заголовками та імпортованими файлами, перевіряйте прив'язку параметрів на боці сервера та allowlist-и, використовуйте облікові записи бази даних з мінімальними привілеями та переконайтеся, що помилки й логи не розкривають чутливі деталі запитів чи схеми.

Сильна відповідь включає

  • пояснює межу між кодом і даними
  • розпізнає ризики динамічних ідентифікаторів і надмірних привілеїв
  • тестує непрямі джерела вводу, помилки та логування

Практичні приклади

Тримати user data поза SQL structure
-- SQL text sent to the database
SELECT id, email, role
FROM users
WHERE email = $1;

-- The driver binds $1 separately, for example:
-- value: attacker@example.com' OR '1'='1

Placeholder є частиною фіксованої SQL structure, а введений text передається окремо як value, тому quotes усередині input не стають executable SQL. Dynamic identifiers та ORDER BY fragments все одно потребують allowlist, а не value binding.

Очікуваний результат: Збіг можливий лише для row, email якого буквально дорівнює переданому value.

#sql#database#sql-injection#security

Джерела

OWASP Web Security Testing GuideOWASP · Web application security test coveragePostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
SQL fundamentals & CRUDSeniorCommonTest design

Що потрібно тестувати, коли бізнес-логіка реалізована у view, збережених процедурах (stored procedures) чи тригерах?

Відповідь

Перевіряйте оголошені вхідні дані, форму результату, права доступу, межі транзакцій, побічні ефекти й поведінку при помилках для збережених процедур, а також видимість рядків і актуальність даних для звичайних чи матеріалізованих view. Тригери потребують тестів на кожну операцію, що їх запускає, порядок спрацювання, рекурсію, масові операції, відкат, вимкнений стан і неочікувані записи в аудиторські чи похідні таблиці. Охоплюйте прямий і опосередкований через застосунок доступ, зміни схеми, продуктивність на реалістичному обсязі даних та спостережуваність, щоб прихована логіка в базі даних не перетворилася на непрозорий другий застосунок.

Сильна відповідь включає

  • охоплює права доступу, транзакції та приховані побічні ефекти
  • тестує спрацювання тригера, рекурсію та поведінку при масових операціях
  • включає сумісність схеми, продуктивність і спостережуваність

Практичні приклади

Перевірити view на його public result boundary
CREATE VIEW paid_order_totals AS
SELECT customer_id, SUM(amount) AS total
FROM orders
WHERE status = 'paid'
GROUP BY customer_id;

SELECT *
FROM paid_order_totals
WHERE customer_id = 1;

View потрібно тестувати на result shape, filtering, permissions, freshness і performance як інший public query contract. Procedures і triggers додають inputs, transaction boundaries та hidden side effects, тому потребують додаткових direct і application-mediated tests.

Очікуваний результат: View повертає той самий paid-order total, що й незалежно обчислений source query.

#database#sql#stored-procedures#triggers

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLJuniorCommonPractical

Напишіть SQL, який повертає всі рядки, що входять у дубль бізнес-ключа, а не лише значення дубльованих ключів.

Відповідь

Використайте COUNT(*) OVER (PARTITION BY business_key), щоб зберегти деталізацію кожного рядка й одночасно порахувати записи з тим самим ключем, а потім відфільтруйте цей count у зовнішньому запиті. Це корисніше за звичайний GROUP BY, коли потрібно побачити конкретні проблемні записи та їхні primary key.

Сильна відповідь включає

  • використовує window function або приєднує назад згруповані дублікати
  • залишає видимими початкові рядки та primary key
  • визначає правила для NULL і нормалізації ключів

Практичні приклади

Повернути всі рядки-дублікати
WITH x AS (
  SELECT u.*, COUNT(*) OVER (PARTITION BY email) AS copies
  FROM users u
)
SELECT * FROM x WHERE copies > 1;

Віконний COUNT обчислюється для кожного email, але не згортає рядки. Зовнішній WHERE тому повертає кожен проблемний record разом із primary key та іншими колонками.

Очікуваний результат: Кожен рядок, бізнес-ключ якого зустрічається більше одного разу.

#sql#duplicates#window-functions#data-quality

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Як знайти дублікати для видалення так, щоб детерміновано залишити найновіший запис?

Відповідь

Пронумеруйте rows у кожній групі дубльованого бізнес-ключа через ROW_NUMBER, відсортувавши від найновішого до найстарішого, і додайте стабільний tie-breaker, наприклад primary key. rn = 1 буде survivor, а rn > 1 — кандидатами на видалення. Спочатку перегляньте ці ID, а DELETE виконуйте в контрольованій транзакції.

Сильна відповідь включає

  • використовує ROW_NUMBER з PARTITION BY реального бізнес-ключа
  • використовує детерміноване сортування з tie-breaker
  • переглядає primary key кандидатів до деструктивного DML

Практичні приклади

Preview кандидатів на видалення
WITH ranked AS (
  SELECT id, ROW_NUMBER() OVER (
    PARTITION BY email ORDER BY updated_at DESC, id DESC
  ) AS rn
  FROM users
)
SELECT id FROM ranked WHERE rn > 1;

ROW_NUMBER визначає одного survivor для кожної duplicate group. ORDER BY має містити стабільний tie-breaker, щоб однакові timestamp не робили вибір випадковим.

Очікуваний результат: Лише primary key, які є кандидатами на перевірку перед видаленням.

#sql#duplicates#window-functions#dml

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Напишіть SQL, який повертає найновіше замовлення, статус або подію для кожної сутності.

Відповідь

Використайте ROW_NUMBER з partition для кожної сутності, відсортуйте rows від найновіших і залиште row number = 1 у зовнішньому query. Додайте детерміновану вторинну колонку sorting, наприклад primary key, оскільки MAX(timestamp) повертає лише timestamp і при ties може відповідати кільком records.

Сильна відповідь включає

  • використовує ROW_NUMBER з PARTITION BY ключа сутності
  • сортує від найновіших із детермінованим tie-breaker
  • пояснює, чому MAX(timestamp) сам по собі не повертає один повний row

Практичні приклади

Останній row для кожної сутності
WITH ranked AS (
  SELECT o.*, ROW_NUMBER() OVER (
    PARTITION BY customer_id ORDER BY created_at DESC, id DESC
  ) AS rn
  FROM orders o
)
SELECT * FROM ranked WHERE rn = 1;

PARTITION BY починає ranking заново для кожної сутності. Найновіший row отримує rn = 1, а secondary key детерміновано розв'язує однакові timestamps.

Очікуваний результат: Рівно один найновіший row для кожної сутності, яка має дані.

#sql#window-functions#latest-row#ranking

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLJuniorCommonPractical

Напишіть SQL, який повертає друге за величиною унікальне значення salary, score або amount.

Відповідь

Присвойте values ранги за спаданням через DENSE_RANK і виберіть rank = 2. DENSE_RANK підходить, коли однакові values займають одну й ту саму позицію. Для саме другого унікального value також можна взяти MAX, менший за загальний MAX, але ranking легше узагальнити.

Сильна відповідь включає

  • трактує однакові values як один rank
  • використовує DENSE_RANK або коректний nested MAX
  • розрізняє ranking унікальних values і нумерацію rows

Практичні приклади

Друге за величиною унікальне value
WITH r AS (
  SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS pos
  FROM employees
  WHERE salary IS NOT NULL
)
SELECT DISTINCT salary FROM r WHERE pos = 2;

DENSE_RANK надає однаковим non-NULL values одну позицію без gaps, тому pos = 2 означає second-highest distinct salary. NULL виключено явно, щоб DBMS-specific NULL ordering не змінював answer.

Очікуваний результат: Друге за величиною distinct value, якщо воно існує.

#sql#ranking#window-functions#aggregation

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Напишіть SQL, який повертає Top N rows у кожній категорії, команді або customer group.

Відповідь

Використайте ranking window function з PARTITION BY ключа group та ORDER BY потрібної metric, а потім відфільтруйте rank у зовнішньому query. ROW_NUMBER поверне рівно N rows на group, тоді як RANK або DENSE_RANK можуть включити додаткові rows при ties — залежно від business rule.

Сильна відповідь включає

  • розбиває ranking за group через PARTITION BY
  • усвідомлено обирає ROW_NUMBER проти RANK або DENSE_RANK
  • використовує детерміноване sorting, коли потрібна точна кількість rows

Практичні приклади

Top N усередині кожної group
WITH r AS (
  SELECT p.*, ROW_NUMBER() OVER (
    PARTITION BY category_id ORDER BY revenue DESC, id
  ) AS rn
  FROM products p
)
SELECT * FROM r WHERE rn <= 3;

Window ranking створює незалежну послідовність для кожної group. ROW_NUMBER дає точну кількість rows, а RANK/DENSE_RANK потрібні, коли ties мають залишатися разом.

Очікуваний результат: До N rows на group або більше, якщо обрана rank function навмисно включає ties.

#sql#top-n#window-functions#ranking

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Як написати SQL для порівняння expected dataset з actual dataset і показати відмінності?

Відповідь

Порівнюйте в обох напрямках: expected EXCEPT actual знаходить missing або changed rows, а actual EXCEPT expected — unexpected rows. У багатьох СУБД EXCEPT має set semantics, тому multiplicity дублікатів потрібно перевіряти окремо. СУБД без EXCEPT можуть використовувати JOIN або NOT EXISTS.

Сильна відповідь включає

  • порівнює expected→actual і actual→expected
  • розуміє set semantics та обмеження duplicate counts
  • адаптує comparison до dialect і NULL semantics

Практичні приклади

Порівняти expected та actual data
SELECT id, status, amount FROM expected
EXCEPT
SELECT id, status, amount FROM actual;

EXCEPT знаходить rows із лівого result set, яких немає у правому. Для повного diff потрібно виконати comparison в обох напрямках; duplicate multiplicity перевіряється окремо.

Очікуваний результат: Нуль differences, коли expected та actual distinct datasets збігаються.

#sql#data-quality#except#reconciliation

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Напишіть SQL для розрахунку running total без згортання початкових rows.

Відповідь

Використайте SUM як window aggregate з PARTITION BY, якщо total починається заново для кожної entity, та ORDER BY у послідовності events. Вкажіть явний ROWS frame і детерміноване sorting, коли кілька rows можуть мати однаковий timestamp, інакше peer rows можуть дати неочікуваний cumulative result.

Сильна відповідь включає

  • використовує SUM як window function, а не GROUP BY
  • визначає partition і детермінований порядок events
  • розуміє вплив window frame

Практичні приклади

Running total для кожного row
SELECT account_id, occurred_at, amount,
       SUM(amount) OVER (
         PARTITION BY account_id
         ORDER BY occurred_at, id
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS running_total
FROM ledger
ORDER BY account_id, occurred_at, id;

SUM працює по ordered window від першого row partition до current row. Явний ROWS frame усуває сюрпризи peer-group, коли кілька rows мають однаковий sort value.

Очікуваний результат: Кожен source row разом із cumulative total на цей момент.

#sql#window-functions#running-total#aggregation

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Напишіть SQL, який знаходить зміну value порівняно з попередньою event тієї самої entity.

Відповідь

Використайте LAG, щоб отримати previous value всередині partition, впорядкованого за event time та стабільним tie-breaker, а потім порівняйте current і previous values у зовнішньому query. First row і NULL потрібно трактувати явно, бо відсутність previous row та реальний NULL інакше можуть виглядати однаково.

Сильна відповідь включає

  • використовує LAG з правильними partition і order
  • фільтрує computed previous value поза window expression
  • явно обробляє first row і NULL semantics

Практичні приклади

Порівняти row з попереднім
WITH x AS (
  SELECT e.*,
         LAG(status) OVER (
           PARTITION BY entity_id ORDER BY occurred_at, id
         ) AS previous_status,
         ROW_NUMBER() OVER (
           PARTITION BY entity_id ORDER BY occurred_at, id
         ) AS rn
  FROM events e
)
SELECT * FROM x
WHERE rn > 1
  AND (
    status <> previous_status
    OR (status IS NULL AND previous_status IS NOT NULL)
    OR (status IS NOT NULL AND previous_status IS NULL)
  );

LAG показує previous status, а ROW_NUMBER відрізняє first event від реального previous NULL. Явні NULL branches знаходять NULL→value та value→NULL transitions без DBMS-specific null-safe comparison operator.

Очікуваний результат: Лише rows, у яких tracked value змінився відносно безпосередньо попереднього row.

#sql#lag#window-functions#change-detection

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Напишіть SQL для пошуку gaps в очікуваній numeric sequence, наприклад invoice number або event version.

Відповідь

Впорядкуйте sequence і використайте LAG, щоб порівняти кожне value з previous. Якщо current value більше за previous + 1, missing interval починається з previous + 1 і закінчується current - 1. Спочатку підтвердьте, що continuity справді є business invariant, бо багато generated identifiers законно можуть мати gaps.

Сильна відповідь включає

  • використовує ordered LAG або еквівалентний gap-detection technique
  • повертає missing interval, а не лише flag row
  • перевіряє, чи sequence continuity є реальною requirement

Практичні приклади

Знайти gaps у sequence
WITH x AS (
  SELECT sequence_no,
         LAG(sequence_no) OVER (ORDER BY sequence_no) AS prev_no
  FROM events
)
SELECT prev_no + 1 AS gap_start, sequence_no - 1 AS gap_end
FROM x
WHERE sequence_no > prev_no + 1;

Кожен sequence value порівнюється з predecessor. Різниця більше одиниці означає gap, а арифметика previous + 1 / current - 1 повертає його межі.

Очікуваний результат: Один row для кожного missing numeric interval.

#sql#lag#sequence#data-quality

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLJuniorCommonPractical

Напишіть один SQL query, який рахує кілька conditional counts, наприклад passed, failed та error.

Відповідь

Використайте conditional aggregation: перетворіть кожну condition на 1 або 0 через CASE і підсумуйте values, за потреби додавши GROUP BY за build, suite, day чи іншою dimension. Це усуває окремі queries для кожного status і дозволяє звірити subtotal counts із загальною кількістю rows.

Сильна відповідь включає

  • використовує SUM з CASE або equivalent filtered aggregate
  • може додати GROUP BY для metrics по build чи team
  • звіряє conditional totals із загальною population

Практичні приклади

Кілька conditional counts одним query
SELECT
  SUM(CASE WHEN status = 'passed' THEN 1 ELSE 0 END) AS passed,
  SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
  SUM(CASE WHEN status = 'error'  THEN 1 ELSE 0 END) AS errors
FROM test_results;

Кожен CASE повертає 1 лише для matching rows, а SUM рахує їх. GROUP BY дозволяє повторити ті самі calculations незалежно для кожного build або іншої group.

Очікуваний результат: Один row на group із total та counts по кожній condition.

#sql#aggregation#case#qa-metrics

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Як за допомогою SQL виявити, що JOIN неочікувано розмножив parent rows?

Відповідь

Порівняйте parent population до та після JOIN на потрібному grain і згрупуйте joined result за parent primary key, щоб знайти keys, які з'явилися більше одного разу. Типові causes — legitimate one-to-many relationship, неповний join predicate, duplicated dimension rows або неправильний grain; DISTINCT може приховати symptom, але не виправляє logic.

Сильна відповідь включає

  • перевіряє row counts і parent-key multiplicity до та після JOIN
  • відрізняє one-to-many behavior від неповного join condition
  • не використовує DISTINCT як стандартний repair

Практичні приклади

Знайти rows, розмножені JOIN
SELECT p.id, COUNT(*) AS joined_rows
FROM parents p
JOIN children c ON c.parent_id = p.id
GROUP BY p.id
HAVING COUNT(*) > 1;

На expected grain має бути один output row на parent. GROUP BY parent key після JOIN показує keys із count > 1, які можуть завищувати SUM або COUNT.

Очікуваний результат: Parent keys, для яких joined cardinality більша за один.

#sql#joins#cardinality#data-quality

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Напишіть стабільний next-page SQL query без використання великого OFFSET.

Відповідь

Використайте keyset pagination: sort за стабільним unique tuple і запитуйте rows після останнього tuple попередньої page. Це не потребує сканувати дедалі більший OFFSET і зменшує duplicate або skipped rows, коли між page requests додаються нові records. Ordering має бути детермінованим, зазвичай timestamp плюс primary key.

Сильна відповідь включає

  • використовує last seen ordering key замість OFFSET
  • використовує unique deterministic ordering tuple
  • пояснює stability і performance benefits при changing data

Практичні приклади

Stable next page через keyset pagination
SELECT id, customer_id, status, created_at
FROM orders
WHERE created_at < :last_created_at
   OR (created_at = :last_created_at AND id < :last_id)
ORDER BY created_at DESC, id DESC
LIMIT :limit;

Використовуйте last seen tuple (created_at, id) як cursor і тримайте predicate узгодженим із deterministic descending ORDER BY. Playground використовує SQLite LIMIT syntax і sample parameter values.

Очікуваний результат: Наступна bounded page після налаштованого sample cursor у deterministic descending order.

#sql#pagination#performance#ordering

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
Practical SQLMiddleCommonPractical

Напишіть SQL для розрахунку percentage кожної group від overall total.

Відповідь

Спочатку aggregate data до потрібного group grain, а потім поділіть total кожної group на window SUM по aggregated rows. Використайте cast або decimal multiplier, якщо database інакше виконає integer division, і визначте behavior для zero total та NULL amounts.

Сильна відповідь включає

  • спочатку агрегує до intended grain
  • використовує window total замість другого independent query
  • обробляє numeric type, zero denominator і NULL behavior

Практичні приклади

Percentage group від overall total
SELECT category_id,
       SUM(amount) AS amount,
       100.0 * SUM(amount)
         / NULLIF(SUM(SUM(amount)) OVER (), 0) AS pct_total
FROM sales
GROUP BY category_id;

Спочатку query створює one row per group, після чого window SUM обчислює grand total по цих grouped rows. Decimal arithmetic і NULLIF захищають від integer division та division by zero.

Очікуваний результат: Один row на group з aggregate та її share від grand total.

#sql#aggregation#window-functions#percentage

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
ETL, warehouse & BIMiddleCommonScenario

Як ви перевіряли б відповідність source-to-target мапінгу та data lineage (простежуваність даних) у трансформаційному пайплайні?

Відповідь

Почніть із версійованого мапінгу, який фіксує кожне поле джерела, правило трансформації, поле призначення та допустимі винятки. Перевіряйте репрезентативні рядки та агрегатні звірки (reconciliation) на кожній межі пайплайна, щоб розбіжність можна було локалізувати, а не виявити лише у фінальному звіті. Враховуйте відхилені записи, значення за замовчуванням, конвертації типів, джойни та lineage-метадані, а також зберігайте докази, що пов'язують вхідні дані джерела з опублікованим результатом.

Сильна відповідь включає

  • використовує версійований мапінг на рівні полів як оракул (еталон)
  • поєднує перевірки на рівні рядків з агрегатною звіркою
  • зберігає простежувані докази на кожному етапі трансформації

Практичні приклади

Локалізувати source-to-target row differences
SELECT
  COALESCE(s.order_id, t.order_id) AS order_id,
  s.amount AS source_amount,
  t.amount AS target_amount
FROM source_orders s
FULL OUTER JOIN warehouse_orders t ON t.order_id = s.order_id
WHERE s.order_id IS NULL
   OR t.order_id IS NULL
   OR s.amount IS DISTINCT FROM t.amount;

Full comparison показує rows, відсутні з будь-якого боку, і змінені values на визначеному grain бізнес-ключа. Oracle має бути versioned mapping: якщо target amount трансформується, порівнюйте його з незалежно обчисленим expected value, а не просто з source value.

Очікуваний результат: Лише пояснювані mapping differences; unexplained rows вказують, який етап pipeline потрібно дослідити.

#database#etl#lineage#mapping

Джерела

Add data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contractsDevelop and test Dataflow pipelinesGoogle Cloud · Unit, integration and end-to-end testing for batch and streaming pipelinesUnderstand star schema and its importance for Power BIMicrosoft · Facts, dimensions, grain, relationships and semantic-model quality
ETL, warehouse & BISeniorCommonScenario

Як ви тестували б batch- та streaming-пайплайни на пізні (late), дубльовані та не впорядковані (out-of-order) події?

Відповідь

Спершу визначте правила продукту щодо event-time, processing-time, дедуплікації, watermark та replay, перш ніж формувати очікувані результати. Ін'єктуйте пізні, повторні, переупорядковані та відсутні події навколо меж вікон (window boundaries), а потім перевіряйте як проміжний стан, так і фінальні агрегати після завершення допустимого лагу (allowed lateness). Повторне відтворення (replay) того самого вхідного потоку має давати задокументований ідемпотентний результат, а операційні метрики повинні показувати відкинуті, затримані або поміщені в карантин записи.

Сильна відповідь включає

  • розрізняє event time та processing time
  • тестує watermark, replay та ідемпотентність на межах вікон
  • перевіряє як результати даних, так і операційні докази/метрики

Практичні приклади

Знайти duplicate event IDs перед перевіркою aggregates
WITH ranked AS (
  SELECT
    event_id,
    event_time,
    ingested_at,
    ROW_NUMBER() OVER (
      PARTITION BY event_id
      ORDER BY ingested_at, event_id
    ) AS copy_no
  FROM raw_events
)
SELECT event_id, event_time, ingested_at
FROM ranked
WHERE copy_no > 1
ORDER BY event_id, ingested_at;

Streaming tests потребують контрольованих late, duplicate та reordered inputs. Цей SQL check показує repeated event IDs у raw input; далі pipeline assertion має довести, що задокументовані deduplication, watermark і replay rules не дозволяють цим copies подвоїти фінальний aggregate.

Очікуваний результат: Відомі injected duplicates видимі в raw data, але downstream враховуються лише згідно з documented deduplication rule.

#data#streaming#batch#events

Джерела

Develop and test Dataflow pipelinesGoogle Cloud · Unit, integration and end-to-end testing for batch and streaming pipelinesAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contractsGreat Expectations CoreGreat Expectations · Data-quality expectations, validation definitions and evidence
ETL, warehouse & BISeniorCommonScenario

Як ви тестували б дата-пайплайн на стійкість до schema drift (дрейфу схеми даних у джерелі) та змін контракту?

Відповідь

Розглядайте схему продюсера та політику сумісності як версійований контракт, що охоплює назви полів, типи, nullability, значення за замовчуванням та семантичне значення. Перевіряйте адитивні, видалені, перейменовані та несумісні зміни на старих і нових консюмерах, а також переконайтеся, що пайплайн свідомо приймає, трансформує, поміщає в карантин або відхиляє кожну версію. Додайте контрактні перевірки (contract checks) перед деплоєм і моніторинг несподіваних форм даних у продакшені, щоб мовчазне пошкодження даних не могло пройти під виглядом успішного job.

Сильна відповідь включає

  • перевіряє структурну та семантичну сумісність
  • охоплює змішані версії продюсера та консюмера
  • запобігає мовчазному пошкодженню даних завдяки ранній валідації та моніторингу

Практичні приклади

Порівняти actual schema з expected contract
WITH expected(column_name, data_type, is_nullable) AS (
  VALUES
    ('user_id', 'bigint', 'NO'),
    ('email', 'text', 'YES'),
    ('created_at', 'timestamp with time zone', 'NO')
),
actual AS (
  SELECT column_name, data_type, is_nullable
  FROM information_schema.columns
  WHERE table_schema = 'public'
    AND table_name = 'users'
)
SELECT
  COALESCE(e.column_name, a.column_name) AS column_name,
  e.data_type AS expected_type,
  a.data_type AS actual_type,
  e.is_nullable AS expected_nullable,
  a.is_nullable AS actual_nullable
FROM expected e
FULL OUTER JOIN actual a ON a.column_name = e.column_name
WHERE e.column_name IS NULL
   OR a.column_name IS NULL
   OR e.data_type <> a.data_type
   OR e.is_nullable <> a.is_nullable;

Schema drift — це проблема contract, а не лише parser error. До processing порівнюйте наявність fields, types і nullability, а окремо тестуйте semantic changes, renamed fields, defaults та mixed producer/consumer versions.

Очікуваний результат: Нуль rows, коли physical schema відповідає expected structural contract.

#data#schema#contracts#compatibility

Джерела

Develop and test Dataflow pipelinesGoogle Cloud · Unit, integration and end-to-end testing for batch and streaming pipelinesAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contractsPostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recovery
ETL, warehouse & BIMiddleCommonTheory

Що таке slowly changing dimensions (повільно змінювані виміри), і як ви тестували б правила збереження їхньої історії?

Відповідь

Slowly changing dimension (SCD) визначає, як атрибути в сховищі даних обробляються при зміні бізнес-сутності — наприклад, перезаписом поточного значення (SCD Type 1) або створенням нового датованого історичного рядка (SCD Type 2). Тестуйте випадки без змін, перші зміни, повторні, заднім числом (backdated) та множинні зміни, перевіряючи surrogate keys, effective dates, прапорці поточного рядка (current-row flag) та джойни від фактів до правильної версії виміру. Повторний запуск того самого завантаження (load) не повинен створювати дубльовану історію, а звіти за минулі дати мають зберігати задуманий історичний сенс.

Сильна відповідь включає

  • розрізняє поведінку перезапису та збереження історії
  • перевіряє effective dates, прапорці поточного рядка та surrogate keys
  • перевіряє ідемпотентність та коректність історичної звітності

Практичні приклади

Знайти overlapping history ranges у SCD Type 2
WITH history AS (
  SELECT
    customer_id,
    valid_from,
    valid_to,
    is_current,
    LEAD(valid_from) OVER (
      PARTITION BY customer_id
      ORDER BY valid_from
    ) AS next_valid_from
  FROM dim_customer
)
SELECT *
FROM history
WHERE next_valid_from IS NOT NULL
  AND (valid_to IS NULL OR valid_to > next_valid_from);

SELECT customer_id,
       SUM(CASE WHEN is_current = TRUE THEN 1 ELSE 0 END) AS current_rows
FROM dim_customer
GROUP BY customer_id
HAVING SUM(CASE WHEN is_current = TRUE THEN 1 ELSE 0 END) <> 1;

Якщо validity ranges є half-open [valid_from, valid_to), Type 2 history не повинна overlap, а кожна tracked entity має мати рівно одну current version. Aggregation усіх history rows, а не попередній filter лише current rows, дозволяє другому check знайти як zero, так і multiple current versions.

Очікуваний результат: Обидва validation queries повертають нуль rows для узгодженої Type 2 dimension.

#database#warehouse#dimensions#history

Джерела

Understand star schema and its importance for Power BIMicrosoft · Facts, dimensions, grain, relationships and semantic-model qualityAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contracts
SQL fundamentals & CRUDMiddleCommonPractical

Як слід поводитися з персональними та іншими чутливими даними під час тестування баз даних?

Відповідь

За замовчуванням використовуйте синтетичні або незворотно замасковані дані та збирайте лише ті атрибути, які справді потрібні для мети тестування. Якщо дані, отримані з продакшену, виняткового необхідні, застосовуйте задокументовану авторизацію, мінімізацію, шифрування, контроль доступу, обмеження термінів зберігання та аудит-логування для вивантажень, бекапів і результатів тестів. Перевіряйте, що маскування зберігає потрібні зв'язки між даними, не розкриваючи оригінальних значень, і включайте до обсягу тестування сценарії видалення, експорту та несанкціонованого доступу.

Сильна відповідь включає

  • надає перевагу синтетичним або незворотно замаскованим тестовим даним (fixtures)
  • застосовує принцип найменших привілеїв, мінімізацію даних та контроль термінів зберігання
  • тестує як поведінку приватності, так і практичну придатність даних

Практичні приклади

Перевірити environment-specific masking rule
-- Example test environment rule: all synthetic/masked emails use example.invalid
SELECT id, email
FROM test_customers
WHERE email IS NOT NULL
  AND email NOT LIKE '%@example.invalid';

-- Find non-NULL references whose parent row is missing
SELECT o.id
FROM test_orders o
LEFT JOIN test_customers c ON c.id = o.customer_id
WHERE o.customer_id IS NOT NULL
  AND c.id IS NULL;

Віддавайте перевагу synthetic data. Якщо masked dataset дозволений, перевіряйте masking contract і збережені relationships без відновлення чи порівняння відкритих production values. Конкретне marker rule має відповідати задокументованій masking implementation цього environment.

Очікуваний результат: Немає rows, що порушують documented masking rule, і немає зламаних required relationships.

#database#privacy#test-data#security

Джерела

OWASP Application Security Verification StandardOWASP · Testable application-security requirements and assurance levelsPayment Card Industry Data Security StandardPCI Security Standards Council · Payment-data security controls, testing evidence and continuous complianceEudraLex Volume 4, Annex 11 — Computerised SystemsEuropean Commission · GMP computerized-system validation, data integrity, change and continuity controls
ETL, warehouse & BISeniorCommonScenario

Як ви тестували б семантичні показники (measures) BI-системи в розрізі часових поясів і фінансових календарів?

Відповідь

Задокументуйте бізнес-визначення кожного показника, включно з гранулярністю (grain), фільтрами, агрегацією, валютою, часовим поясом та правилами фіскального періоду. Звіряйте відомі записи від джерела через модель до візуалізації навколо переходів на літній/зимовий час, меж місяців і років, неповних періодів та винятків фіскального календаря. Порівнюйте підсумки на кількох рівнях деталізації (drill levels) та для різних ролей доступу, щоб коректний загальний підсумок не приховував помилкове нарізання даних (slicing), проблеми безпеки чи неправильне присвоєння періоду.

Сильна відповідь включає

  • спирається на явну бізнес-семантику, а не на візуальну схожість результату
  • тестує часові та фіскальні межі за допомогою контрольованих (відомих) записів
  • звіряє підсумки на різних рівнях деталізації та для різних ролей доступу

Практичні приклади

Звірити measure за fiscal period
SELECT
  d.fiscal_year,
  d.fiscal_period,
  SUM(f.net_amount) AS net_revenue
FROM fact_sales f
JOIN dim_date d ON d.date_key = f.date_key
WHERE d.calendar_date >= DATE '2026-06-25'
  AND d.calendar_date <  DATE '2026-07-08'
GROUP BY d.fiscal_year, d.fiscal_period
ORDER BY d.fiscal_year, d.fiscal_period;

Використовуйте governed date dimension, а не обчислюйте fiscal periods ad hoc у тесті. Звіряйте source facts на boundary dates і повторюйте comparison для time zones, DST transitions, incomplete periods та role/filter contexts, які використовує BI model.

Очікуваний результат: Fiscal-period totals відповідають documented measure і calendar rules semantic model.

#bi#analytics#measures#calendars

Джерела

Understand star schema and its importance for Power BIMicrosoft · Facts, dimensions, grain, relationships and semantic-model qualityAdd data tests to your DAGdbt Labs · Reusable assertions for analytics transformations and data contractsGreat Expectations CoreGreat Expectations · Data-quality expectations, validation definitions and evidence
Transactions & concurrencyMiddleCommonTheory

Які ризики бази даних мають покривати інтеграційні тести, окрім перевірки наявності рядка?

Відповідь

Перевіряйте обмеження (constraints), транзакції, ізоляцію, значення за замовчуванням, міграції, референційну цілісність, конкурентні оновлення, кодування, часові пояси, поля аудиту та поведінку відкату. Валідуйте через підтримувані інтерфейси, якщо прямий доступ до бази даних явно не є метою тесту.

Сильна відповідь включає

  • цілісність і транзакції
  • конкурентність і міграції
  • відповідний рівень спостереження

Практичні приклади

Перевірити цілісність БД, а не лише наявність row
-- Orphaned foreign-key relationships should not exist
SELECT o.id AS order_id, o.customer_id
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE c.id IS NULL;

-- Required audit/default fields should satisfy their invariants
SELECT id, created_at, updated_at
FROM orders
WHERE created_at IS NULL
   OR updated_at IS NULL
   OR updated_at < created_at;

Database integration check має перевіряти invariants, а не лише те, що INSERT створив row. SQL допомагає знайти зламані relationships та некоректний persisted state; окремими тестами перевіряйте rollback транзакцій, isolation/concurrency, migrations, encoding і time-zone behavior через підтримувану межу застосунку або БД.

Очікуваний результат: У sample fixture перший query навмисно повертає orphan order; у здоровому production integrity check очікується zero rows. Persisted-state query має повернути zero rows.

#database#transactions#integrity

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryDOU — QA interview: 250+ questionsDOU · Coverage signal across Junior, Middle, Senior, AQA, web, mobile and practical topics
Indexes & query performanceJuniorCommonPractical

Як би ви пояснили INNER JOIN, LEFT JOIN, групування й агрегацію під час завдання з валідації даних?

Відповідь

INNER JOIN повертає лише рядки, що збігаються з обох сторін; LEFT JOIN залишає кожен рядок лівої таблиці й додає відповідні дані з правої. GROUP BY формує набори для агрегатних функцій, таких як COUNT чи SUM, тоді як HAVING фільтрує групи вже після агрегації.

Сильна відповідь включає

  • рядки, що збігаються, проти збережених рядків
  • групування перед агрегацією
  • WHERE проти HAVING

Практичні приклади

Порівняти JOIN behavior та агрегувати groups
-- INNER JOIN keeps only customers that have matching orders
SELECT c.id, o.id AS order_id
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;

-- LEFT JOIN keeps every customer, including customers with zero orders
SELECT
  c.id,
  COUNT(o.id) AS order_count,
  COALESCE(SUM(o.total), 0) AS total_spend
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id
HAVING COUNT(o.id) >= 2
ORDER BY total_spend DESC;

INNER JOIN відкидає left-side rows без match; LEFT JOIN зберігає їх і заповнює right-side columns значеннями NULL. GROUP BY створює aggregate group для кожного customer, aggregate functions рахують значення для групи, а HAVING фільтрує групи вже після aggregation. COUNT(o.id), на відміну від COUNT(*), не рахує customer без orders як один joined row.

Очікуваний результат: Перший query містить лише matched customers/orders; другий повертає customers щонайменше з двома orders та їх aggregated spend.

#sql#joins#aggregation

Джерела

PostgreSQL documentationPostgreSQL Global Development Group · Relational modelling, SQL, joins, constraints, transactions, concurrency, indexes, query plans and recoveryDOU — QA interview: 250+ questionsDOU · Coverage signal across Junior, Middle, Senior, AQA, web, mobile and practical topics