SQL Batch Processing — Applied Running Totals and Pivots

ADVBatch ProcessingWindow FunctionsRunning Totals and PivotsRecursive CTEAPI PracticePostgreSQL/BigQuery Compatible5 questions
1 / 5 · In progress 0 / 5 completed
QUESTION 6

Multiple CTEs — Classify Users into Segments with RFM Scores

Multiple CTEsWITHRFMSegment Classification
Background ▼

Listing multiple CTEs separated by commas creates a pipeline in which each later CTE can reference an earlier one. This organizes complex batch processing as a sequence of named steps.

WITH
step1 AS (
  SELECT user_id, COUNT(*) AS order_count
  FROM orders
  GROUP BY user_id
),
step2 AS (               -- can reference step1
  SELECT *,
    CASE WHEN order_count >= 5 THEN 'Loyal' ELSE 'General' END AS segment
  FROM step1
)
SELECT * FROM step2;
What is RFM:a method for scoring users on three metrics: Recency (most recent purchase date), Frequency (purchase frequency), and Monetary (purchase amount). It is extremely common in marketing batch processing.
Problem ▼

From the orders table below, calculate each user’s R (days since the most recent purchase), F (purchase count), and M (total purchase amount), assign each metric a score from 1 to 3, and classify the final segment.

Reference date: 2024-05-01. R: within 30 days = 3, within 60 days = 2, otherwise = 1. F: 3 or more purchases = 3, 2 purchases = 2, 1 purchase = 1. M: at least 100000 = 3, at least 50000 = 2, otherwise = 1.
Classify the final segment as “VIP” when total_score is at least 8, “Loyal” when it is at least 6, and “General” otherwise.

Source table
▸ orders
order_iduser_idorder_dateamount
1U012024-04-2050000
2U012024-04-2870000
3U012024-04-3030000
4U022024-03-10120000
5U022024-03-2540000
6U032024-01-1530000
7U042024-04-25200000
8U042024-04-29150000
Expected Output ▼
user_idr_scoref_scorem_scoretotal_scoresegment
U013339VIP
U043238VIP
U022237Loyal
U031113General
QUESTION 7

DENSE_RANK() — Rank Ties and Return the Top-Ranked Group

DENSE_RANKWindow FunctionsTie handlingRanking API
Background ▼

When tied scores must include everyone through the tied third place, DENSE_RANK() is the right choice. Understand how it differs from ROW_NUMBER.

SELECT player, score,
  ROW_NUMBER()  OVER (ORDER BY score DESC) AS rn,    -- always assign consecutive numbers, even for ties
  RANK()        OVER (ORDER BY score DESC) AS rnk,   -- ties share a rank; the next rank is skipped
  DENSE_RANK()  OVER (ORDER BY score DESC) AS dr;    -- ties share a rank; rank numbers do not skip
Choosing the right function:Use ROW_NUMBER for the top N rows with a fixed count, DENSE_RANK for everyone tied through the top N ranks, and RANK when a displayed ranking should skip numbers. Choose based on the API requirement.
Problem ▼

From the game_scores table below, rank players within each game using DENSE_RANK and return everyone ranked third or higher. Show every tied player at the same rank.

Source table
▸ game_scores
game_idplayer_namescore
G01Alice9500
G01Bob8200
G01Carol8200
G01Dave7800
G01Eve7100
G02Frank6000
G02Grace6000
G02Hank5500
G02Iris4800
Expected Output ▼
game_idplayer_namescorerank
G01Alice95001
G01Bob82002
G01Carol82002
G01Dave78003
G02Frank60001
G02Grace60001
G02Hank55002
G02Iris48003
QUESTION 8

LEFT JOIN + COALESCE — Build a Complete Monthly Report Including Zero-Sales Months

LEFT JOINCOALESCEMissing-data fill-inbatch report
Background ▼

Months with no sales have no rows in the orders table. However, a monthly report may still need to include months with zero sales. LEFT JOIN + COALESCE is the standard pattern.

SELECT
  m.month,
  COALESCE(s.total_sales, 0) AS total_sales  -- replace NULL with 0
FROM       months        m        -- master list of all months
LEFT JOIN  sales_summary s        -- exists only for months with sales (some months become NULL)
        ON m.month = s.month;    -- join by month
LEFT JOIN vs INNER JOIN:INNER JOIN returns only matching rows from both tables, so zero-sales months disappear. LEFT JOIN preserves every row from the left table and returns NULL when the right side has no match. Converting that NULL to 0 with COALESCE is the standard zero-sales fill-in idiom.
Problem ▼

Create a monthly summary for every month from January through June 2024. For months with no sales, set total_sales = 0 and order_count = 0.

Source table
▸ orders (sales data)
order_idorder_monthamount
12024-0130000
22024-0150000
32024-0380000
42024-0540000
52024-0560000
62024-0690000
▸ month_master (month master)
month
2024-01
2024-02
2024-03
2024-04
2024-05
2024-06
Expected Output ▼
monthtotal_salesorder_count
2024-01800002
2024-0200
2024-03800001
2024-0400
2024-051000002
2024-06900001
QUESTION 9

FIRST_VALUE() / LAST_VALUE() — Put First and Latest Purchases on One Row

FIRST_VALUELAST_VALUEFirst/latestPurchase analysis
Background ▼

FIRST_VALUE() and LAST_VALUE() return values from the first and last rows in a window. They are useful for purchase-history analysis such as showing each user’s first and latest product on one row.

FIRST_VALUE(product) OVER (
  PARTITION BY user_id          -- independent for each user
  ORDER BY     order_date        --  days → of rowsis  timespurchase
) AS first_product

LAST_VALUE(product) OVER (
  PARTITION BY user_id
  ORDER BY     order_date
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -- look at all rows — important
) AS last_product
LAST_VALUE pitfall:If ROWS BETWEEN is omitted, the default is “through the current row,” so rows before the last cannot see the latest value.ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING in “allrows”please。
Problem ▼

From the purchase_history table below, return each user’s first purchase date, first product, latest purchase date, and latest product on one row. Return exactly one row per user.

Source table
▸ purchase_history
user_idorder_dateproduct
U012024-01-10Apple
U012024-02-20Banana
U012024-04-05Orange
U022024-01-15Green tea
U022024-03-30Coffee
U032024-02-01Water
Expected Output ▼
user_idfirst_datefirst_productlast_datelast_product
U012024-01-10Apple2024-04-05Orange
U022024-01-15Green tea2024-03-30Coffee
U032024-02-01Water2024-02-01Water
QUESTION 10

Recursive CTE (RECURSIVE) — Expand Hierarchical Categories Across All Levels

RECURSIVERecursive CTEHierarchyTree expansion
Background ▼

A recursive CTE (WITH RECURSIVE) is a self-referencing query. It expands data with hierarchical parent-child relationships, such as category trees, organization charts, and comment threads.

WITH RECURSIVE tree AS (

  -- Anchor: get the first row (root node)
  SELECT id, parent_id, name, 0 AS depth
  FROM   categories
  WHERE  parent_id IS NULL          -- no parent = root

  UNION ALL                          -- connect the anchor and recursive members (do not remove duplicates)

  -- Recursive member: use the previous step to get children
  SELECT c.id, c.parent_id, c.name, t.depth + 1
  FROM       categories c
  INNER JOIN tree       t  -- reference tree from the previous step (recursive)
          ON c.parent_id = t.id
)
SELECT * FROM tree;
Recursive termination:Recursion ends when the recursive member produces no new rows. A cycle such as A → B → A can loop forever, so production queries should enforce a maximum depth.
Problem ▼

The categories table below stores parent-child relationships for product categories. Expand every level with a recursive CTE and display each category’s depth and hierarchy path (for example: Electronics > Smartphone > Android).

Source table
▸ categories
idparent_idname
1NULLElectronics
21Smartphone
31PC
42Android
52iPhone
63Notebook PC
73Desktop
Expected Output ▼
idnamedepthpath
1Electronics0Electronics
2Smartphone1Electronics > Smartphone
3PC1Electronics > PC
4Android2Electronics > Smartphone > Android
5iPhone2Electronics > Smartphone > iPhone
6Notebook PC2Electronics > PC > Notebook PC
7Desktop2Electronics > PC > Desktop