runtime
78 / 88

lesson 78 Warehouse & reporting projects · advanced

Integrate CRM + ERP with source authority

Combine CRM and ERP using clear source ownership rules.

the idea

Normalize source identifiers under a declared contract: strip one leading NAS in ERP people and hyphens in ERP locations. Keep the latest CRM customer; source_row breaks equal timestamps. CRM gender wins when meaningful; ERP supplies a fallback. Preserve every valid CRM customer. Validate birth dates against fixed 2025-04-01.

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

example

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

example.sql
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

Watch for: COALESCE does not know whether n/a is meaningful. Normalize sentinel codes to NULL before applying authority. Each ERP key must be unique or the join can fan out.

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 crm_people;
  2. Execute the transformation. Normalize source identifiers under a declared contract: strip one leading NAS in ERP people and hyphens in ERP locations. Keep the latest CRM customer; source_row breaks equal timestamps. CRM gender wins when meaningful; ERP supplies a fallback. Preserve every valid CRM customer. Validate birth dates against fixed 2025-04-01.

    step-2.sql
    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
  3. Test another question. COALESCE does not know whether n/a is meaningful. Normalize sentinel codes to NULL before applying authority. Each ERP key must be unique or the join can fan out.

    step-3.sql
    WITH integrated 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
    )
    SELECT gender,COUNT(*) AS customer_count
    FROM integrated
    GROUP BY gender

challenge 1

Return customer_id,name,gender,country,birth_date using the integration contract in Understand. Use latest normalized CRM keys, CRM-first gender, ERP fallback, mapped country, and valid nonfuture birth date.

Return customer_idnamegendercountrybirth_dateany row order

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