Sqlism
Loading…
Learning path

Become a Data QA / ETL Test Engineer with SQL

Every number on a dashboard passed through a pipeline that somebody had to prove correct. ETL testers and data QA engineers are those people, and their main tool isn't a test framework. It's SQL, written to compare what came in with what came out.

8 Modules
~7 hrs, self-paced
3 Portfolio Projects
0 of 8 modules complete

This path follows a real ETL test cycle: profile the source, validate every rule in the source-to-target mapping, reconcile counts and totals, hunt for duplicates and NULLs, test slowly changing dimensions and incremental loads, compare environments for regressions, and trace a wrong number back to its root cause. It's a hands-on skill set that's in steady demand in services companies, banks and insurers, and almost nobody teaches it in a structured way.

  1. 1

    Profile the Source Before You Test

    30 min

    You can't write good test cases for data you haven't looked at. Start every test cycle by profiling the source: row counts, distinct counts on keys, NULL and blank counts, min/max ranges and value frequencies. It tells you which tests matter and stops you blaming the ETL for problems that were already in the source.

    SELECT COUNT(*)                                      AS total_rows,
           COUNT(DISTINCT policy_id)                     AS distinct_keys,
           SUM(CASE WHEN premium IS NULL THEN 1 ELSE 0 END) AS null_premiums,
           MIN(start_date) AS min_start, MAX(start_date) AS max_start
    FROM src_policies;
    Save the profiling results with the test evidence. When a defect is disputed later, "the source already had 42 NULL premiums" settles it in seconds.
  2. 2

    Validate the Source-to-Target Mapping

    40 min

    The mapping document is your test specification. Each row says how a target column is derived from the source. Turn every rule into a query that applies the rule to the source independently and compares the result with what the ETL actually loaded. Any mismatch is a defect.

    -- Rule: target.gender = 'Male'/'Female' from source codes 'M'/'F'
    SELECT s.emp_no, s.gndr, t.gender
    FROM src_emp s
    JOIN dim_employee t ON t.employee_id = CAST(SUBSTR(s.emp_no, 2) AS INTEGER)
    WHERE t.gender <> CASE s.gndr WHEN 'M' THEN 'Male' WHEN 'F' THEN 'Female' END;
    Test transformations independently of the developer's code. If you copy their SQL into your test, you'll faithfully reproduce their bug and the test will pass.
  3. 3

    Reconcile Counts, Totals & Rows

    40 min

    Reconciliation proves nothing was lost or duplicated in transit. Compare row counts, then totals of key measures, then the actual rows with EXCEPT in both directions. Documented exclusions (rejected rows, filtered records) must explain every difference exactly.

    -- Rows in source that never reached the target
    SELECT policy_id, premium FROM src_policies
    EXCEPT
    SELECT policy_id, premium FROM tgt_policies;
    
    -- Rows in target that don't exist in source (unexpected inserts)
    SELECT policy_id, premium FROM tgt_policies
    EXCEPT
    SELECT policy_id, premium FROM src_policies;
    Matching counts don't prove matching data. Two wrong rows can cancel out in a count; always add a totals check and a row-level comparison.
  4. 4

    Duplicate, NULL & Constraint Testing

    30 min

    Keys must be unique and mandatory columns must be filled. Write a standard set of checks for every target table: duplicate business keys, NULLs in not-null columns, orphaned foreign keys and values outside the accepted list. These catch most loading defects before business users do.

    SELECT policy_id, COUNT(*) AS copies
    FROM tgt_policies
    GROUP BY policy_id
    HAVING COUNT(*) > 1;
    Test for duplicates on the business key, not just the surrogate key. A table can have perfectly unique IDs and still contain the same policy three times.
  5. 5

    Testing SCD & Incremental Loads

    35 min

    History and incremental logic hide the subtlest bugs. For SCD Type 2, check that each business key has exactly one current row, validity windows don't overlap and there are no gaps. For incremental loads, re-run the same batch and confirm nothing changes, and confirm late-arriving or updated rows are picked up.

    -- Each customer must have exactly one current version
    SELECT customer_id, COUNT(*) AS current_rows
    FROM dim_customer
    WHERE is_current = 1
    GROUP BY customer_id
    HAVING COUNT(*) <> 1;
    Always run an idempotency test: load the same batch twice. If row counts or totals change the second time, the load isn't safe to re-run.
  6. 6

    Regression Testing: Dev vs Prod Comparisons

    30 min

    When the ETL code changes, everything that shouldn't change must stay identical. Run the new code in a test environment and compare its output with production: counts per partition, totals per key dimension, then a row-level diff that shows exactly which columns changed.

    SELECT COALESCE(d.region, p.region) AS region,
           p.total_premium AS prod_total, d.total_premium AS dev_total
    FROM (SELECT region, SUM(premium) AS total_premium FROM dev.fct_policies GROUP BY region) d
    FULL OUTER JOIN
         (SELECT region, SUM(premium) AS total_premium FROM prod.fct_policies GROUP BY region) p
      ON p.region = d.region
    WHERE COALESCE(d.total_premium, 0) <> COALESCE(p.total_premium, 0);
    Compare aggregates first to find where differences are, then row-level diffs only for those slices. Diffing a billion rows column by column is rarely the fastest path.
  7. 7

    Automating Checks in the Pipeline

    30 min

    Manual test cycles catch problems once; automated checks catch them every day. Turn your best test queries into a suite that runs after every load and records results in a table, so failures alert the team and you build a history of data quality over time.

    INSERT INTO qa_results (run_date, check_name, failing_rows)
    SELECT CURRENT_DATE, 'tgt_policies duplicate keys',
           (SELECT COUNT(*) FROM (SELECT policy_id FROM tgt_policies
                                  GROUP BY policy_id HAVING COUNT(*) > 1) d);
    Classify checks as blocking (stop the load, e.g. duplicate keys) or warning (alert only, e.g. a 10% volume drop) so the pipeline doesn't halt over minor anomalies.
  8. 8

    Defect Triage: Tracing a Wrong Number

    30 min

    "The report total is wrong" is where a tester earns their keep. Work backwards layer by layer: report, mart, staging, source. Compare the number at each hop until you find the first layer where it changes. The cause is almost always a filter, a join fan-out, a NULL, or a grain mismatch.

    A good defect report includes the exact query, the expected vs actual value, the layer where they diverge and a sample of affected keys. That turns a two-day back-and-forth into a one-hour fix.

Practice on real projects

Practice the full test cycle on realistic data, the way you would on a real ETL project.

Get interview-ready

ETL testing interviews are practical: expect "write a query to find records missing in the target", duplicate detection, SCD scenarios and how you would test a given mapping rule.

Validate what you've learned

25 questions covering everything above. Score 80% or higher to earn your Data QA / ETL Test Engineer badge on your dashboard.

Upgrade to Sqlism Pro to take the validation quiz

25 questions · pass at 80%+ to earn your Data QA / ETL Test Engineer badge on your dashboard. One-time payment, lifetime access.

Get Sqlism Pro