Skip to main content

Data Quality Checks and Quality Gates

Level 6: Level 6: Cloud & Data Engineering Foundationsmedium24 mindata qualityduplicate detectionnull-rate / completeness checksquality gates (write-audit-publish)conditional aggregation

A pipeline can succeed and still write garbage. The check families a junior is expected to name (uniqueness, completeness, validity, volume, referential integrity) and the quality gate that stops bad data, all written as plain SQL.

"Validated" is not a wish, it is a set of checks

The Silver layer earned the words "cleaned, validated, deduplicated" in the last lesson, and this lesson is where that word gets teeth. A pipeline can finish with a green status and still write garbage: a broken upstream export sends half the rows, a join fans out and duplicates every order, a bug lets NULLs into a key column. "The job succeeded" and "the data is correct" are two different questions, and a data engineer is expected to check the second one. "How do you know your data is correct, and how would you test a pipeline?" is one of the most common junior DE interview questions.

The check families

Data-quality checks fall into a handful of families, and almost every real test is one of these:

Table
Every data-quality test is one of these families, and each is a plain SQL query underneath a tool like dbt or Great Expectations.
check familycatchesSQL shape
uniqueness / duplicatesa key that repeats (broken dedup, fan-out join)GROUP BY key HAVING COUNT(*) > 1
completeness / null ratemissing values in a required columnSUM(CASE WHEN col IS NULL ...) / COUNT(*)
validity / rangevalues outside the allowed domainWHERE col < 0 OR col NOT IN (...)
volume anomalya load far bigger or smaller than usualcompare COUNT(*) to a recent baseline
referential integritya foreign key with no parent rowLEFT JOIN dim WHERE dim.id IS NULL
Every data-quality test is one of these families, and each is a plain SQL query underneath a tool like dbt or Great Expectations.
  • Uniqueness / duplicates. A key that should be unique (one row per order_id) but is not signals a broken dedup or a fan-out join. This is also how you catch a non-idempotent re-run: run a job twice without an idempotent write and every key doubles. You find it with GROUP BY key HAVING COUNT(*) > 1.
  • Completeness / null rate. A column that should always be present but is NULL or blank. You measure the null rate and alert when it crosses a threshold.
  • Validity / range. A value outside its allowed domain: a negative price, a status not in the allowed set, a date in the future.
  • Volume / row-count anomaly. Today's load is a tenth of yesterday's, or triple. The row count itself is a signal (the rows_out your run log records).
  • Referential integrity. A user_id in the fact table that does not exist in the users dimension (an orphan).

Quality gates (naming what you already built)

You have already written both halves of this. In Level 4 you wrote the four dbt tests (unique, not_null, accepted_values, relationships) as zero-rows-means-pass queries, and in Level 5 you built a write-audit-publish (WAP) gate that staged a batch, audited it with checks, and published only on a clean pass. The vocabulary consolidates here: a quality gate runs those checks and fails the pipeline (or quarantines the batch) when one breaches its threshold, so bad data never reaches the Gold tables the dashboards read. Frameworks like dbt tests or Great Expectations are how you wire these into production, but every one of them is a SQL query underneath, exactly the ones you wrote by hand.

Writing the checks with SQL

The checks are exactly the SQL you already know: a GROUP BY ... HAVING for duplicates, a conditional SUM for a null rate, a CASE for a pass or fail gate. The exercises run real checks over a staged_events batch: find the duplicated ids, then compute a null rate and decide whether the batch passes its gate.

Common mistake: running quality checks only after the data is published. By then the bad data is already in the Gold tables the dashboards read, so the checks belong in a gate that runs before publish (write-audit-publish).

Interview nuance: "how would you test this pipeline" wants the check families by name (uniqueness, completeness, validity, volume, referential integrity) and the idea of a quality gate that fails the run. Bonus points for naming dbt tests or Great Expectations, and for saying the checks run in a write-audit-publish step before the data is published.

On a real platform this differs. Real quality checks run as dbt tests or Great Expectations suites wired into the pipeline, emitting pass or fail per check with row-level samples of the failures. The staged_events table here lets you write the same checks as plain SQL so you can see what each framework runs for you underneath.

Sample data for this example
CREATE TABLE staged_events (
  event_id INTEGER,   -- should be unique per event
  user_id  INTEGER,
  amount   REAL,
  country  TEXT
);
INSERT INTO staged_events (event_id, user_id, amount, country) VALUES
  (1, 100, 12.50, 'US'),
  (2, 101, 30.00, 'US'),
  (3, NULL, 15.00, 'CA'),   -- null user_id (completeness issue)
  (4, 102, -5.00, 'GB'),    -- negative amount (validity issue)
  (5, 103, 20.00, 'US'),
  (2, 101, 30.00, 'US'),    -- duplicate event_id 2
  (5, 103, 20.00, 'US'),    -- duplicate event_id 5
  (5, 103, 20.00, 'US'),    -- event_id 5 appears three times
  (6, NULL,  8.00, NULL),   -- null user_id and null country
  (7, 104, 40.00, 'US');
Worked example (SQL)
-- A one-row quality scorecard for the staged batch before it is published.
SELECT COUNT(*) AS total_rows,
       SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END) AS null_user_ids,
       SUM(CASE WHEN amount <= 0 THEN 1 ELSE 0 END) AS bad_amounts
FROM staged_events;

Apply

Your turn

The task this lesson builds to.

Write a query that returns every event_id that appears more than once, with how many copies it has, as (event_id, copies), most copies first, over staged_events(event_id, user_id, amount, country).

event_id should be unique per event, so any id with more than one row is a duplicate a dedup step must resolve. Order by copies descending, then event_id.

3 hints and 1 automated check are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, plus 4 bonus drills.

Write a query that returns the batch's total row count, its null user_id rate as a percentage, and whether it passes a quality gate, as (total_rows, null_user_pct, gate), over the same staged_events table.

The gate requires the null user_id rate to be under 5 percent: return 'pass' when it is and 'fail' otherwise. Round null_user_pct to 2 decimals.

3 hints and 1 automated check are waiting in the workspace.