Reporting databases are designed differently from application databases. This guide covers the data-warehouse SQL that data and ETL testers meet: slowly changing dimensions (how history is kept), OLTP vs OLAP, star and snowflake schemas, and subtotal queries with ROLLUP, CUBE and GROUPING SETS.

Slowly Changing Dimensions (SCD)

What are Slowly Changing Dimensions?

In data warehousing, dimension data (customers, products, employees) changes slowly over time.

For example: a customer moves city, an employee changes department, a product changes price.

The question is: HOW do you handle these changes in your data warehouse?

SCDs define the strategy — and all three types are asked in data engineering interviews.

Asked at: Amazon, Flipkart, Walmart Labs, Myntra, any company with a data warehouse.

Advertisement

SCD Type 1 — Overwrite (No History)

In simple terms

Type 1 is like using a whiteboard — when something changes, you just ERASE and WRITE the new value.

Old value is gone forever. No history kept.

Use when history does not matter: fixing a typo in a name, updating a phone number.

Before update:

customer_idnamecity
101Naveed KhanBangalore

After SCD Type 1 update (customer moved to Hyderabad):

customer_idnamecity
101Naveed KhanHyderabad
-- SCD Type 1: Simple UPDATE, no history
UPDATE dim_customers
SET city = 'Hyderabad',
    updated_at = NOW()
WHERE customer_id = 101;

-- Pros: Simple, storage-efficient
-- Cons: Historical data permanently lost
-- Use: Correcting errors, non-analytical attributes (phone, email typos)

SCD Type 2 — Add New Row (Full History) ← Most Important

In simple terms

Type 2 is like keeping ALL old pages of a notebook instead of erasing.

When Naveed moves from Bangalore to Hyderabad, we add a NEW row.

The old row is marked as "expired". The new row is marked as "current".

Now we can query: "Where did Naveed live in 2023?" — history is preserved!

This is the most commonly used SCD type in real data warehouses.

SCD Type 2 Table Design:

customer_idnamecityeffective_fromeffective_tois_current
101Naveed KhanBangalore2022-01-012024-06-300
101Naveed KhanHyderabad2024-07-019999-12-311
-- SCD Type 2: Expire old record, insert new record
START TRANSACTION;

-- Step 1: Expire the current record
UPDATE dim_customers
SET effective_to = DATE_SUB(CURDATE(), INTERVAL 1 DAY),
    is_current = FALSE
WHERE customer_id = 101 AND is_current = TRUE;

-- Step 2: Insert new current record
INSERT INTO dim_customers
    (customer_id, name, city, effective_from, effective_to, is_current)
VALUES
    (101, 'Naveed Khan', 'Hyderabad', CURDATE(), '9999-12-31', TRUE);

COMMIT;

-- Query: What city was customer 101 in on 2023-06-01?
SELECT city FROM dim_customers
WHERE customer_id = 101
  AND '2023-06-01' BETWEEN effective_from AND effective_to;
-- Returns: Bangalore ✓

-- Query: Current city of all customers
SELECT customer_id, name, city FROM dim_customers WHERE is_current = TRUE;

-- Surrogate key approach (better for joins)
-- dim_customers: surrogate_key (PK), customer_id (natural key), ...
-- fact_orders references surrogate_key, not customer_id
-- This way the fact row always points to the correct dimension snapshot

SCD Type 3 — Add New Column (Limited History)

In simple terms

Type 3 keeps only the CURRENT and ONE PREVIOUS value in extra columns.

When Naveed moves, we save the old city in "prev_city" and update "city".

Simple but limited — can only track ONE change back, not full history.

SCD Type 3 Table:

customer_idnamecurrent_cityprev_citycity_changed_at
101Naveed KhanHyderabadBangalore2024-07-01
-- SCD Type 3: Shift current to previous, update current
UPDATE dim_customers
SET prev_city       = current_city,
    current_city    = 'Hyderabad',
    city_changed_at = CURDATE()
WHERE customer_id = 101;

-- Pros: Simple queries for before/after comparison
-- Cons: Only one historical value stored, more schema changes needed for each new attribute
SCD TypeHistory KeptComplexityStorageBest For
Type 1 (Overwrite)NoneVery simpleMinimalFixing errors, non-analytical attrs
Type 2 (New Row)Full historyMediumHigh (grows over time)Most analytics — who did what when
Type 3 (New Column)Previous onlySimpleLowBefore/after comparison only
Type 4 (History table)Full history (separate)MediumMediumKeeping main table lean + audit log
Type 6 (Hybrid 1+2+3)Full + current flag + prev colComplexHighestEnterprise DWH with all requirements

Star Schema, Snowflake and Data Warehouse SQL

When Is This Asked?

Every data analyst, analytics engineer, data engineer, and senior backend SQL interview.

Companies: Amazon, Flipkart, Walmart, Myntra, Meesho, Razorpay, any company with BI/analytics.

The interviewer wants to know you understand OLAP vs OLTP and can design for reporting.

OLTP vs OLAP

FeatureOLTP (Operational)OLAP (Analytical)
PurposeDay-to-day transactionsBusiness intelligence & reporting
Query typeSimple, fast, CRUDComplex, aggregated, read-heavy
Data sizeGBsTBs to PBs
NormalizationHighly normalised (3NF)Denormalised (star/snowflake)
DesignMany tables, many JOINsFew wide tables, fewer JOINs
LatencyMillisecondsSeconds to minutes
ExamplesMySQL, PostgreSQL (production DB)Redshift, BigQuery, Snowflake, Hive
Update frequencyContinuous (per transaction)Batch loads (daily/hourly ETL)

Star Schema

In simple terms

A Star Schema looks like a star: one big FACT table in the middle, surrounded by smaller DIMENSION tables.

FACT table: stores measurable events/numbers — sales amounts, clicks, orders, revenue.

DIMENSION tables: describe the who/what/when/where — customers, products, dates, locations.

The fact table has foreign keys pointing to all dimension tables.

It is called "star" because the diagram looks like a star!

Star Schema: E-Commerce Sales Data Warehouse

TableTypeColumnsPurpose
fact_salesFACTsale_id, date_key, customer_key, product_key, store_key, quantity, revenue, discountOne row per sale — the core metrics
dim_dateDIMENSIONdate_key, date, day, week, month, quarter, year, is_holiday, fiscal_yearAll dates for time-series analysis
dim_customerDIMENSIONcustomer_key, customer_id, name, city, state, tier, signup_dateCustomer attributes (SCD Type 2)
dim_productDIMENSIONproduct_key, product_id, name, category, sub_category, brand, price_bandProduct catalogue attributes
dim_storeDIMENSIONstore_key, store_id, name, city, region, store_typeStore/channel attributes
-- Create Star Schema tables
CREATE TABLE dim_date (
    date_key     INT PRIMARY KEY,   -- e.g., 20250115 (YYYYMMDD)
    full_date    DATE NOT NULL,
    day_of_week  VARCHAR(10),
    week_number  INT,
    month_num    INT,
    month_name   VARCHAR(10),
    quarter      INT,
    year         INT,
    is_weekend   BOOLEAN,
    is_holiday   BOOLEAN,
    fiscal_year  INT,
    fiscal_qtr   INT
);

CREATE TABLE fact_sales (
    sale_id      BIGINT PRIMARY KEY AUTO_INCREMENT,
    date_key     INT REFERENCES dim_date(date_key),
    customer_key INT REFERENCES dim_customer(customer_key),
    product_key  INT REFERENCES dim_product(product_key),
    store_key    INT REFERENCES dim_store(store_key),
    quantity     INT NOT NULL,
    unit_price   DECIMAL(10,2),
    discount_pct DECIMAL(5,2) DEFAULT 0,
    revenue      DECIMAL(12,2),
    cost         DECIMAL(12,2)
);

-- Typical Star Schema Query: Revenue by Category and Quarter
SELECT
    d.year, d.quarter, p.category,
    SUM(f.revenue)   AS total_revenue,
    SUM(f.quantity)  AS units_sold,
    COUNT(DISTINCT f.sale_id) AS transactions
FROM fact_sales f
JOIN dim_date     d ON f.date_key     = d.date_key
JOIN dim_product   p ON f.product_key  = p.product_key
WHERE d.year = 2025
GROUP BY d.year, d.quarter, p.category
ORDER BY d.quarter, total_revenue DESC;

Snowflake Schema

Star vs Snowflake

Snowflake Schema: dimension tables are further normalised into sub-dimensions.

Example: In Star Schema, dim_product has category directly.

In Snowflake Schema: dim_product has a category_key → dim_category (separate table).

Star Schema: Denormalised, fewer JOINs, faster queries, more storage, easier to understand.

Snowflake Schema: Normalised, more JOINs, saves storage, harder to query, better data integrity.

Most real data warehouses use Star Schema for performance.

FeatureStar SchemaSnowflake Schema
NormalisationDenormalised dimensionsNormalised dimensions (sub-tables)
Number of tablesFewerMore
JOIN complexitySimplerMore complex
Query performanceFaster (fewer JOINs)Slower (more JOINs)
StorageMore (redundant data)Less (normalised)
BI Tool SupportBetter (simpler model)Works but more config
Use caseMost analytical use casesWhen storage is a major constraint

Rollup, Cube, Grouping Sets

What are these?

ROLLUP: Generates subtotals and a grand total along a hierarchy.

CUBE: Generates all possible combinations of subtotals.

GROUPING SETS: Specify exactly which grouping combinations you want.

All three are extensions of GROUP BY for multi-dimensional aggregation.

-- ROLLUP: subtotals by dept, then overall total
SELECT dept, city,
       COUNT(*) AS emp_count,
       SUM(salary) AS total_salary
FROM employees
GROUP BY ROLLUP(dept, city);
-- Produces:
--   Engineering | Bangalore | 10 | 850000
--   Engineering | Mumbai    |  5 | 420000
--   Engineering | NULL      | 15 | 1270000  ← dept subtotal
--   HR          | Delhi     |  8 | 480000
--   HR          | NULL      |  8 | 480000   ← dept subtotal
--   NULL        | NULL      | 23 | 1750000  ← grand total

-- GROUPING() function: detect which NULLs are from ROLLUP (not real NULLs)
SELECT
    CASE GROUPING(dept) WHEN 1 THEN 'ALL DEPTS' ELSE dept END AS dept,
    CASE GROUPING(city) WHEN 1 THEN 'ALL CITIES' ELSE city END AS city,
    SUM(salary) AS total
FROM employees
GROUP BY ROLLUP(dept, city);

-- CUBE: ALL combinations of dept + city
SELECT dept, city, SUM(salary) AS total
FROM employees
GROUP BY CUBE(dept, city);
-- Produces: (dept,city), (dept,ALL), (ALL,city), (ALL,ALL)

-- GROUPING SETS: choose exactly which groupings you want
SELECT dept, city, month, SUM(salary)
FROM employees
GROUP BY GROUPING SETS (
    (dept, city),   -- group by dept and city
    (dept),         -- group by dept only
    ()              -- grand total
);
-- More flexible than ROLLUP or CUBE

FAQs

What is SCD Type 2?

A slowly changing dimension that keeps full history: when an attribute changes, the current row is closed with an end date and a new current row is inserted.

What is the difference between a star and a snowflake schema?

In a star schema each dimension is one denormalised table joined directly to the fact table; a snowflake schema normalises dimensions into several related tables, saving space at the cost of more joins.

What does ROLLUP do in SQL?

It adds subtotal rows for each level of the GROUP BY columns plus a grand total; CUBE adds subtotals for every combination of the columns.