Digital Stackroom data validation pipeline infographic showing raw data sources, SQL assertion checks for nulls, uniqueness, referential integrity, freshness, thresholds, and a validated production report.

Automating Data Validation Pipelines: Writing SQL Assertion Checks Before Running Production Reports

In modern analytics engineering, bad data compounds rapidly. An upstream application schema change, a silent API webhook failure, or an incomplete ETL sync can inject null foreign keys, negative currency totals, or duplicated transaction rows into your core analytical tables.

If downstream reporting layers run automatically on a set schedule without prior validation, executive dashboards display inaccurate numbers before anyone notices—a major failure mode when building actionable Looker Studio executive dashboards the upstream failure. At that point, the cost of remediation involves not just repairing the pipeline, but also issuing corrections to business stakeholders and re-establishing trust.

Automating SQL assertion checks (often called pre-flight data tests or circuit breakers) creates a defensive staging gate. If critical business assumptions fail, the pipeline halts, alerts the engineering team, and leaves historical production tables untouched.

1. Problem Statement: Silent Data Pipeline Failures

Consider a nightly reporting pipeline that aggregates enterprise financial summaries into reporting marts:

[Operational Source] ──► [Ingestion Worker] ──► [Staging Tables] ──► [Executive Reporting Mart]
                                                          ▲
                                                          │
                         No assertion barrier exists here!
                         Silent nulls & duplicate rows pass straight through.

Common Production Failure Modes

  1. The Inadvertent Cartesian Product: A developer updates a dimension join without including a composite key condition, multiplying transaction totals by 3x.
  2. Orphaned Foreign Keys: Deleted customer records leave active billing line items linked to NULL or non-existent parent IDs.
  3. Temporal Inversion: Ingested log timestamps appear in the future due to incorrect server timezone conversions (UTC vs. EST).
  4. Volume Drops: An API silently returns a 200 OK status with an empty JSON array, truncating daily transaction ingestion from 100,000 rows to 0 without throwing an execution exception.

Treating data validation as an active pipeline gate prevents corrupted data from ever reaching production datasets.

2. Anatomy of a Defensive Validation Pipeline

A resilient validation architecture follows a Staging $\rightarrow$ Assertion Gate $\rightarrow$ Atomic Swap workflow:

┌────────────────────────────────────────────────────────┐
│ 1. INGEST INTO STAGING                                 │
│ Raw extracts load into an isolated staging schema      │
│ (e.g., staging.fact_orders_daily)                      │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ 2. EXECUTE SQL ASSERTION SUITE                         │
│ Run targeted validation queries returning fail counts  │
│ • Uniqueness Checks       • Referential Integrity      │
│ • Range / Domain Rules    • Row Volume Thresholds      │
└──────────────────────────┬─────────────────────────────┘
                           │
             ┌─────────────┴─────────────┐
             │ Are any assertions broken?│
             └─────────────┬─────────────┘
              YES          │          NO
               │           │           │
               ▼           │           ▼
┌────────────────────────┐ │ ┌───────────────────────────┐
│ HALT & ALERT           │ │ │ 3. ATOMIC PROMOTION       │
│ Abort transaction,     │ │ │ Move staging records into │
│ trigger Slack/PagerDuty│ │ │ production table mart     │
└────────────────────────┘ │ └───────────────────────────┘

The core principle is simple: A production table is never updated in place until its staging precursor has passed 100% of defined assertion tests.

3. The Core Assertion Suite: 5 Must-Have SQL Checks

Every automated assertion framework should evaluate five fundamental categories of data integrity:

Check CategoryVerification TargetFailure ThresholdExample Analytical Risk
1. UniquenessPrimary & composite keysCOUNT(*) > 0 duplicatesDouble-counted revenue in executive dashboards.
2. NullabilityRequired dimension/metric IDsCOUNT(*) > 0 nullsOrphaned records dropped by inner joins.
3. Domain & RangeRealistic values (e.g., amount $\ge 0$)Value falls outside valid boundariesNegative order totals skewing gross margin.
4. Referential IntegrityChild foreign keys map to parent dimensionsKey exists in fact but not dimensionBroken dimension lookups and missing categorization.
5. Volume AnomalyDaily ingestion count vs. historical averageScans deviate beyond expected deviationSilent upstream API failures or dropped webhook payloads.

4. Hands-On SQL Implementation: Building the Assertion Framework

Let’s write production-ready assertion queries in PostgreSQL that return a count of defective rows. In an automated test suite, any returned row count greater than zero indicates a failure.

Schema Setup: Staging Table

SQL

-- Staging table holding fresh nightly ingestion batch
CREATE TABLE staging_daily_transactions (
    transaction_id BIGINT,
    account_id INT,
    order_date DATE,
    amount NUMERIC(10, 2),
    currency VARCHAR(3),
    order_status VARCHAR(20)
);

Assertion 1: Primary Key Uniqueness Audit

Verify that transaction_id contains zero duplicate occurrences within the batch:

SQL

-- Assertion 1: Duplicate Transaction IDs
-- EXPECTED RESULT: 0 rows returned
SELECT 
    transaction_id, 
    COUNT(*) AS occurrence_count
FROM staging_daily_transactions
GROUP BY transaction_id
HAVING COUNT(*) > 1;

Assertion 2: Critical Column Nullability & Domain Boundaries

Verify that non-nullable columns have values and that financial metrics respect logical domain boundaries (e.g., transaction amounts must be greater than zero, and currencies must match ISO-4217 formats):

SQL

-- Assertion 2: Null Checks & Domain Constraints
-- EXPECTED RESULT: 0 rows returned
SELECT 
    transaction_id,
    account_id,
    amount,
    currency
FROM staging_daily_transactions
WHERE transaction_id IS NULL
   OR account_id IS NULL
   OR amount <= 0.00
   OR currency NOT IN ('USD', 'EUR', 'GBP', 'INR', 'CAD');

Assertion 3: Orphaned Referential Integrity Check

Ensure every account_id in the staging transactions table exists in the enterprise customer dimension table:

SQL

-- Assertion 3: Missing Parent Accounts (Referential Orphan Check)
-- EXPECTED RESULT: 0 rows returned
SELECT 
    s.transaction_id,
    s.account_id
FROM staging_daily_transactions s
LEFT JOIN dim_accounts a ON s.account_id = a.account_id
WHERE a.account_id IS NULL;

Assertion 4: Temporal Anomaly Check (Future Timestamps)

Verify that no transactions contain dates set in the future or dates older than the platform’s historical operating window:

SQL

-- Assertion 4: Unrealistic Date Bounds
-- EXPECTED RESULT: 0 rows returned
SELECT 
    transaction_id,
    order_date
FROM staging_daily_transactions
WHERE order_date > CURRENT_DATE
   OR order_date < '2020-01-01';

Assertion 5: Row Volume Thresholds (Statistical Anomaly Detection)

Compare today’s staging ingestion volume against the 7-day rolling average to catch silent API drop-offs:

SQL

-- Assertion 5: Volume Variance Outlier (> 50% drop compared to 7-day average)
-- EXPECTED RESULT: 0 rows returned
WITH historical_average AS (
    SELECT 
        AVG(daily_rows) AS baseline_volume
    FROM (
        SELECT 
            metric_date, 
            daily_rows 
        FROM pipeline_audit_log 
        WHERE metric_date >= CURRENT_DATE - INTERVAL '7 days'
    )
),
current_staging_volume AS (
    SELECT COUNT(*) AS today_volume 
    FROM staging_daily_transactions
)
SELECT 
    today_volume,
    baseline_volume,
    ROUND(((today_volume - baseline_volume) / NULLIF(baseline_volume, 0)) * 100, 2) AS pct_variance
FROM current_staging_volume, historical_average
WHERE today_volume < (baseline_volume * 0.50); -- Triggers failure if volume drops over 50%

5. Automating the Pipeline Circuit Breaker in Python

Running assertions manually in a SQL terminal does not protect scheduled jobs. We can encapsulate these assertions inside a lightweight Python pipeline runner using psycopg2 or SQLAlchemy.

If any assertion returns defective rows, the script throws an exception, issues an alert, and rolls back the promotion transaction.

Python

import sys
import logging
import psycopg2

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")

DATABASE_URL = "postgresql://analytics_user:secure_pass@localhost:5432/warehouse"

ASSERTIONS = [
    {
        "name": "Check Primary Key Uniqueness",
        "sql": """
            SELECT transaction_id 
            FROM staging_daily_transactions 
            GROUP BY transaction_id 
            HAVING COUNT(*) > 1;
        """
    },
    {
        "name": "Check Nulls & Range Constraints",
        "sql": """
            SELECT transaction_id 
            FROM staging_daily_transactions 
            WHERE transaction_id IS NULL 
               OR account_id IS NULL 
               OR amount <= 0;
        """
    },
    {
        "name": "Check Orphaned Accounts",
        "sql": """
            SELECT s.transaction_id 
            FROM staging_daily_transactions s
            LEFT JOIN dim_accounts a ON s.account_id = a.account_id
            WHERE a.account_id IS NULL;
        """
    }
]

def run_pipeline():
    conn = psycopg2.connect(DATABASE_URL)
    conn.autocommit = False # Open explicit transaction block
    cursor = conn.cursor()

    try:
        logging.info("Executing staging pre-flight assertion suite...")
        
        for check in ASSERTIONS:
            cursor.execute(check["sql"])
            failing_records = cursor.fetchall()
            fail_count = len(failing_records)

            if fail_count > 0:
                logging.error(f"ASSERTION FAILED: {check['name']} found {fail_count} violations.")
                raise ValueError(f"Integrity check failed: {check['name']}")
            
            logging.info(f"PASS: {check['name']}")

        # Atomic Promotion: Runs ONLY if all assertions pass
        logging.info("All assertions passed. Promoting staging data to production...")
        cursor.execute("""
            INSERT INTO prod_daily_transactions 
            SELECT * FROM staging_daily_transactions;
            
            TRUNCATE TABLE staging_daily_transactions;
        """)

        conn.commit()
        logging.info("Pipeline executed and committed successfully.")

    except Exception as e:
        conn.rollback() # Rollback staging promotion
        logging.critical(f"Pipeline aborted due to data integrity failure: {e}")
        # In production, dispatch Slack/PagerDuty webhook here
        sys.exit(1)

    finally:
        cursor.close()
        conn.close()

if __name__ == "__main__":
    run_pipeline()

6. Architecture Checklist for Production Data Quality

  1. Assert on Staging, Never Production: Execute tests against an isolated staging schema or temporary table before promoting records to public reporting layers.
  2. Treat Warnings Differently from Hard Halts: Categorize checks into Hard Blocks (null primary keys, duplicates) that abort the pipeline, and Soft Warnings (slight distribution skews) that log an audit entry without stopping execution.
  3. Persist Audit Logs: Store historical assertion results (test name, execution timestamp, fail count) in a dedicated pipeline_audit_log table for compliance tracking and SLA monitoring.
  4. Enforce Atomic Promotions: Always wrap validation and table swaps inside a single database transaction (BEGIN...COMMIT) so failure states leave production untouched.

By implementing automated SQL assertions as circuit breakers between ingestion and presentation, data engineering teams protect reporting integrity and maintain stakeholder trust at scale.

Scroll to Top