runtime
88 / 88

lesson 88 Warehouse & reporting projects · advanced

Capstone: integrated customers to trusted sales

Combine integration and quality checks into trusted sales reporting.

the idea

Build two reusable temporary outputs in one script: integrated CRM/ERP customers and validated sales lines with existing dimension keys. Report at customer grain, preserving customers with no valid sales. The catalog states field authority and exercise sales rules. This is a runnable miniature of the architecture, not a deployed enterprise pipeline.

tables in this lesson

The challenges use this data. Each column shows its type underneath; → marks a link to another table.

crm_people5 columns · 6 rows
source_rowinteger · keycustomer_idtextnametextgendertextupdated_attext
1 C001 AliceF2025-01-01
2C001Alice NguyenF2025-01-05
3C002Benn/a2025-01-02
4C003CaraF2025-01-03
5C004DevNULL2025-01-04

showing 5 of 6 rows

erp_people3 columns · 5 rows
erp_idtextgendertextbirth_datetext
NASC001Male1990-05-12
NASC002Male1988-02-29
NASC003Female2028-01-01
NASC004Other1995-06-20
NASC999Female1980-01-01

all 5 rows

erp_locations2 columns · 4 rows
erp_idtextcountry_codetext
C-001USA
C-002GB
C-003UK
C-004IND

all 4 rows

bronze_sales6 columns · 13 rows
row_idinteger · keycustomer_idtextproduct_idtextquantitytextsalestextorder_datetext
1C001P012242025-01-01
2 C002 P031802025-01-02
3C001P021452025-01-15
4C003P0422402025-02-01
5C004P0321602025-02-05

showing 5 of 13 rows

dim_customer4 columns · 5 rows
customer_keyinteger · keycustomer_idtextnametextcountrytext
1C001Alice NguyenUnited States
2C002BenUnited Kingdom
3C003CaraUnited Kingdom
4C004DevIndia
5C005ElenaCanada

all 5 rows

dim_product5 columns · 4 rows
product_keyinteger · keyproduct_idtextnametextcategorytextpricereal
1P01NotebookStationery12
2P02Desk lampHome45
3P03KeyboardTech80
4P04HeadphonesTech120

all 4 rows

example

The idea in one query. Press Run to see what it returns; running examples never affects your progress.

example.sql
CREATE TEMP TABLE integrated_customers AS
WITH crm AS(
  WITH ranked AS (
    SELECT
      *,
      ROW_NUMBER() OVER(PARTITION BY TRIM(customer_id) ORDER BY updated_at DESC,source_row DESC) AS rn
    FROM crm_people
    WHERE customer_id IS NOT NULL AND TRIM(customer_id)<>''
  )
  SELECT
    TRIM(customer_id) AS customer_id,
    TRIM(name) AS name,
    CASE UPPER(TRIM(gender))
      WHEN 'F' THEN 'Female'
      WHEN 'M' THEN 'Male'
      ELSE NULL
    END AS crm_gender
  FROM ranked
  WHERE rn=1
),erp AS(
  SELECT
    CASE
      WHEN SUBSTR(TRIM(erp_id),1,3)='NAS' THEN SUBSTR(TRIM(erp_id),4)
      ELSE TRIM(erp_id)
    END AS customer_id,
    CASE UPPER(TRIM(gender))
      WHEN 'F' THEN 'Female'
      WHEN 'FEMALE' THEN 'Female'
      WHEN 'M' THEN 'Male'
      WHEN 'MALE' THEN 'Male'
      ELSE NULL
    END AS erp_gender,
    birth_date
  FROM erp_people
),loc AS(
  SELECT
    REPLACE(TRIM(erp_id),'-','') AS customer_id,
    CASE UPPER(TRIM(country_code))
      WHEN 'US' THEN 'United States'
      WHEN 'USA' THEN 'United States'
      WHEN 'GB' THEN 'United Kingdom'
      WHEN 'UK' THEN 'United Kingdom'
      WHEN 'IND' THEN 'India'
      ELSE 'Unknown'
    END AS country
  FROM erp_locations
)
SELECT
  c.customer_id,
  c.name,
  COALESCE(c.crm_gender,e.erp_gender,'Unknown') AS gender,
  COALESCE(l.country,'Unknown') AS country,
  CASE
    WHEN e.birth_date GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
      AND date(e.birth_date,'+0 days')=e.birth_date
      AND e.birth_date<='2025-04-01'
      THEN e.birth_date
    ELSE NULL
  END AS birth_date
FROM crm c
LEFT JOIN erp e ON e.customer_id=c.customer_id
LEFT JOIN loc l ON l.customer_id=c.customer_id;
CREATE TEMP TABLE built_sales AS
SELECT
  b.row_id AS sale_key,
  c.customer_key,
  p.product_key,
  CAST(b.quantity AS INTEGER) AS quantity,
  CAST(b.quantity AS INTEGER)*p.price AS sales,
  b.order_date
FROM bronze_sales b
JOIN dim_customer c ON c.customer_id=TRIM(b.customer_id)
JOIN dim_product p ON p.product_id=TRIM(b.product_id)
WHERE TRIM(b.quantity)<>''
  AND TRIM(b.quantity) NOT GLOB '*[^0-9]*'
  AND CAST(b.quantity AS INTEGER)>0
  AND b.order_date GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
  AND date(b.order_date,'+0 days')=b.order_date;
SELECT
  i.customer_id,
  i.name,
  i.gender,
  i.country,
  COUNT(s.sale_key) AS sales_lines,
  COALESCE(SUM(s.sales),0) AS total_sales
FROM integrated_customers i
JOIN dim_customer c ON c.customer_id=i.customer_id
LEFT JOIN built_sales s ON s.customer_key=c.customer_key
GROUP BY i.customer_id,i.name,i.gender,i.country;

Watch for: The final quality checks and catalog belong to the deliverable. A report alone cannot prove lineage, key stability, or replay safety.

build it step by step

Each step changes one thing. Run them in order and watch the result change.

  1. Understand the source contract. Fictional extracts, load counts, invalid dates, versioned dimensions, refresh staging and a data catalog. The foundation project is latest-state; separate history and incremental labs demonstrate extensions.

    step-1.sql
    SELECT *
    FROM warehouse_catalog;
  2. Execute the transformation. Build two reusable temporary outputs in one script: integrated CRM/ERP customers and validated sales lines with existing dimension keys. Report at customer grain, preserving customers with no valid sales. The catalog states field authority and exercise sales rules. This is a runnable miniature of the architecture, not a deployed enterprise pipeline.

    step-2.sql
    CREATE TEMP TABLE integrated_customers AS
    WITH crm AS(
      WITH ranked AS (
        SELECT
          *,
          ROW_NUMBER() OVER(PARTITION BY TRIM(customer_id) ORDER BY updated_at DESC,source_row DESC) AS rn
        FROM crm_people
        WHERE customer_id IS NOT NULL AND TRIM(customer_id)<>''
      )
      SELECT
        TRIM(customer_id) AS customer_id,
        TRIM(name) AS name,
        CASE UPPER(TRIM(gender))
          WHEN 'F' THEN 'Female'
          WHEN 'M' THEN 'Male'
          ELSE NULL
        END AS crm_gender
      FROM ranked
      WHERE rn=1
    ),erp AS(
      SELECT
        CASE
          WHEN SUBSTR(TRIM(erp_id),1,3)='NAS' THEN SUBSTR(TRIM(erp_id),4)
          ELSE TRIM(erp_id)
        END AS customer_id,
        CASE UPPER(TRIM(gender))
          WHEN 'F' THEN 'Female'
          WHEN 'FEMALE' THEN 'Female'
          WHEN 'M' THEN 'Male'
          WHEN 'MALE' THEN 'Male'
          ELSE NULL
        END AS erp_gender,
        birth_date
      FROM erp_people
    ),loc AS(
      SELECT
        REPLACE(TRIM(erp_id),'-','') AS customer_id,
        CASE UPPER(TRIM(country_code))
          WHEN 'US' THEN 'United States'
          WHEN 'USA' THEN 'United States'
          WHEN 'GB' THEN 'United Kingdom'
          WHEN 'UK' THEN 'United Kingdom'
          WHEN 'IND' THEN 'India'
          ELSE 'Unknown'
        END AS country
      FROM erp_locations
    )
    SELECT
      c.customer_id,
      c.name,
      COALESCE(c.crm_gender,e.erp_gender,'Unknown') AS gender,
      COALESCE(l.country,'Unknown') AS country,
      CASE
        WHEN e.birth_date GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
          AND date(e.birth_date,'+0 days')=e.birth_date
          AND e.birth_date<='2025-04-01'
          THEN e.birth_date
        ELSE NULL
      END AS birth_date
    FROM crm c
    LEFT JOIN erp e ON e.customer_id=c.customer_id
    LEFT JOIN loc l ON l.customer_id=c.customer_id;
    CREATE TEMP TABLE built_sales AS
    SELECT
      b.row_id AS sale_key,
      c.customer_key,
      p.product_key,
      CAST(b.quantity AS INTEGER) AS quantity,
      CAST(b.quantity AS INTEGER)*p.price AS sales,
      b.order_date
    FROM bronze_sales b
    JOIN dim_customer c ON c.customer_id=TRIM(b.customer_id)
    JOIN dim_product p ON p.product_id=TRIM(b.product_id)
    WHERE TRIM(b.quantity)<>''
      AND TRIM(b.quantity) NOT GLOB '*[^0-9]*'
      AND CAST(b.quantity AS INTEGER)>0
      AND b.order_date GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
      AND date(b.order_date,'+0 days')=b.order_date;
    SELECT
      i.customer_id,
      i.name,
      i.gender,
      i.country,
      COUNT(s.sale_key) AS sales_lines,
      COALESCE(SUM(s.sales),0) AS total_sales
    FROM integrated_customers i
    JOIN dim_customer c ON c.customer_id=i.customer_id
    LEFT JOIN built_sales s ON s.customer_key=c.customer_key
    GROUP BY i.customer_id,i.name,i.gender,i.country;
  3. Test another question. The final quality checks and catalog belong to the deliverable. A report alone cannot prove lineage, key stability, or replay safety.

    step-3.sql
    CREATE TEMP TABLE integrated_customers AS
    WITH crm AS(
      WITH ranked AS (
        SELECT
          *,
          ROW_NUMBER() OVER(PARTITION BY TRIM(customer_id) ORDER BY updated_at DESC,source_row DESC) AS rn
        FROM crm_people
        WHERE customer_id IS NOT NULL AND TRIM(customer_id)<>''
      )
      SELECT
        TRIM(customer_id) AS customer_id,
        TRIM(name) AS name,
        CASE UPPER(TRIM(gender))
          WHEN 'F' THEN 'Female'
          WHEN 'M' THEN 'Male'
          ELSE NULL
        END AS crm_gender
      FROM ranked
      WHERE rn=1
    ),erp AS(
      SELECT
        CASE
          WHEN SUBSTR(TRIM(erp_id),1,3)='NAS' THEN SUBSTR(TRIM(erp_id),4)
          ELSE TRIM(erp_id)
        END AS customer_id,
        CASE UPPER(TRIM(gender))
          WHEN 'F' THEN 'Female'
          WHEN 'FEMALE' THEN 'Female'
          WHEN 'M' THEN 'Male'
          WHEN 'MALE' THEN 'Male'
          ELSE NULL
        END AS erp_gender,
        birth_date
      FROM erp_people
    ),loc AS(
      SELECT
        REPLACE(TRIM(erp_id),'-','') AS customer_id,
        CASE UPPER(TRIM(country_code))
          WHEN 'US' THEN 'United States'
          WHEN 'USA' THEN 'United States'
          WHEN 'GB' THEN 'United Kingdom'
          WHEN 'UK' THEN 'United Kingdom'
          WHEN 'IND' THEN 'India'
          ELSE 'Unknown'
        END AS country
      FROM erp_locations
    )
    SELECT
      c.customer_id,
      c.name,
      COALESCE(c.crm_gender,e.erp_gender,'Unknown') AS gender,
      COALESCE(l.country,'Unknown') AS country,
      CASE
        WHEN e.birth_date GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
          AND date(e.birth_date,'+0 days')=e.birth_date
          AND e.birth_date<='2025-04-01'
          THEN e.birth_date
        ELSE NULL
      END AS birth_date
    FROM crm c
    LEFT JOIN erp e ON e.customer_id=c.customer_id
    LEFT JOIN loc l ON l.customer_id=c.customer_id;
    CREATE TEMP TABLE built_sales AS
    SELECT
      b.row_id AS sale_key,
      c.customer_key,
      p.product_key,
      CAST(b.quantity AS INTEGER) AS quantity,
      CAST(b.quantity AS INTEGER)*p.price AS sales,
      b.order_date
    FROM bronze_sales b
    JOIN dim_customer c ON c.customer_id=TRIM(b.customer_id)
    JOIN dim_product p ON p.product_id=TRIM(b.product_id)
    WHERE TRIM(b.quantity)<>''
      AND TRIM(b.quantity) NOT GLOB '*[^0-9]*'
      AND CAST(b.quantity AS INTEGER)>0
      AND b.order_date GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
      AND date(b.order_date,'+0 days')=b.order_date;
    SELECT i.country,COALESCE(SUM(s.sales),0) AS total_sales
    FROM integrated_customers i
    JOIN dim_customer c ON c.customer_id=i.customer_id
    LEFT JOIN built_sales s ON s.customer_key=c.customer_key
    GROUP BY i.country

challenge 1

Create TEMP integrated_customers using the integration contract, and TEMP built_sales using the Gold validation rules from lesson Gold: dimensions, facts & quality gates. Return customer_id,name,gender,country,sales_lines,total_sales for valid CRM customers with a dimension match; preserve customers with no valid sales as 0 totals.

Return customer_idnamegendercountrysales_linestotal_salesany row ordertable changes are checked too

challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.