0% found this document useful (0 votes)
3 views26 pages

SQL Reusable Patterns

Uploaded by

Hend Selmy
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views26 pages

SQL Reusable Patterns

Uploaded by

Hend Selmy
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

25 SQL Reusable Patterns

By Chandra venkat

1) Filter records (NULL-safe)


When to use:
Filter users by country
Filter by signup month
Exclude rows with missing phone

Filter Indian users created in Jan-2025 and phone is not NULL.

SELECT *
FROM users
WHERE country = 'India'
AND created_at >= '2025-01-01' AND created_at < '2025-02-01'
AND phone IS NOT NULL;

Chandra Venkat
2) Summarize with totals
When to use:
Get order totals per category
Calculate number of orders per group
Filter out small categories

Summarize each category and keep only those with 10+ orders

SELECT category,
COUNT(*) AS total_orders,
SUM(amount) AS total_amount
FROM orders
GROUP BY category
HAVING COUNT(*) >= 10;

Tip: Use HAVING after GROUP BY to filter aggregates

Chandra Venkat
3) Conditional counts (funnels)

When to use:
Count based on status or stage
Compare Completed vs Cancelled
Build funnel breakdowns

Get number of Completed and Cancelled orders

SELECT
SUM(CASE WHEN status = 'Completed' THEN 1 ELSE 0 END) AS completed_orders,
SUM(CASE WHEN status = 'Cancelled' THEN 1 ELSE 0 END) AS cancelled_orders
FROM orders;

Tip: Conditional `SUM` is more flexible than `COUNT(IF(...))`

Chandra Venkat
4) Matches or misses (anti-join)

When to use:
Find users with no orders
Spot unmatched records
Common audit/debug pattern

Get users who don’t have any matching orders

SELECT u.*
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.user_id
);

Chandra Venkat
5) Combine datasets

When to use:
Combine buyer and seller records
Append same-schema tables
Stack archived vs current

Merge buyers and sellers into one user list

SELECT user_id FROM buyers


UNION ALL
SELECT user_id FROM sellers;

Tip: Use `UNION ALL` if you don’t need duplicate removal

Chandra Venkat
6) Top-N per group (ranking)

When to use:
Top products within each category
Best scores per class/team
Latest N events per user

Get Top 3 products by sales within each category

WITH ranked AS (
SELECT
product_id, category, sales,
ROW_NUMBER() OVER ( PARTITION BY category
ORDER BY sales DESC ) AS rn
FROM product_sales
)
SELECT product_id, category, sales
FROM ranked
WHERE rn <= 3;

Tip: `ROW_NUMBER()` breaks ties arbitrarily; use `RANK()` if ties should share rank.

Chandra Venkat
7) Latest / first per entity

When to use:
Top products within each category
Best scores per class/team
Latest N events per user

Get Top 3 products by sales within each category

WITH r AS (
SELECT
user_id, login_time,
ROW_NUMBER() OVER (PARTITION BY user_id
ORDER BY login_time DESC ) AS rn
FROM logins
)
SELECT user_id, login_time
FROM r
WHERE rn = 1;

Tip: Add filters (e.g., last 90 days) inside the CTE to reduce scan cost.

Chandra Venkat
8) Nth event per entity

When to use:
2nd order date per customer
3rd login per user
Nth subscription renewal

Return the 2nd order per customer

WITH r AS (
SELECT
customer_id, order_date,
ROW_NUMBER() OVER ( PARTITION BY customer_id
ORDER BY order_date ) AS rn
FROM orders
)
SELECT customer_id, order_date
FROM r
WHERE rn = 2;

Tip: Change the `ORDER BY` to `DESC` for “Nth latest”.

Chandra Venkat
9) Running / cumulative totals

When to use:
Cumulative revenue
Running user signups
Progressive counts

Compute running revenue over time

SELECT
dt,
revenue,
SUM(revenue) OVER (
ORDER BY dt
ROWS UNBOUNDED PRECEDING
) AS running_rev
FROM daily_revenue;

Tip: Use `PARTITION BY` to reset the running total per group.

Chandra Venkat
10) Rolling / moving window

When to use:
7-day moving average
Rolling sums by date
Sliding window metrics

Get 7-day moving average of revenue

SELECT
dt,
revenue,
AVG(revenue) OVER (
ORDER BY dt
ROWS 6 PRECEDING
) AS avg_7d
FROM daily_revenue;

Tip: For true calendar windows (not row counts),


pre-expand dates to daily grain without gaps.

Chandra Venkat
11) Trend vs previous / next

When to use:
7-day moving average
Rolling sums by date
Sliding window metrics

Get 7-day moving average of revenue

SELECT
dt,
revenue,
revenue - LAG(revenue) OVER (ORDER BY dt) AS diff_prev,
ROUND(
100 * (revenue - LAG(revenue) OVER (ORDER BY dt))
/ NULLIF(LAG(revenue) OVER (ORDER BY dt), 0),
2
) AS pct_change
FROM daily_revenue;

Tip: Wrap the denominator with `NULLIF(...,0)` to avoid divide-by-zero.

Chandra Venkat
12) Cohorts & retention

When to use:
Group users by signup month
Build cohort tables
High-level retention rollups

Count users per signup month (cohort)

SELECT
DATE_FORMAT(signup_dt, '%Y-%m-01') AS cohort_month,
COUNT(*) AS users
FROM users
GROUP BY cohort_month
ORDER BY cohort_month;

Tip: Use a derived table of month “buckets” to join with activity for retention matrices.

Chandra Venkat
13) Funnel conversion (multi-stage)

When to use:
View → Add → Buy counts
Distinct user tallies per step
Stage-to-stage drop-offs

Distinct users who Viewed, Added, Purchased

SELECT
COUNT(DISTINCT CASE WHEN step='View' THEN user_id END) AS viewers,
COUNT(DISTINCT CASE WHEN step='Add' THEN user_id END) AS adders,
COUNT(DISTINCT CASE WHEN step='Purchase' THEN user_id END) AS buyers
FROM user_steps;

Tip: Prefer one tallies query over multiple joins—simpler and faster.

Chandra Venkat
14) Percent of total / contribution

When to use:
Category mix/share
% of total revenue
Composition by segment

Category share of total sales

WITH agg AS (
SELECT category, SUM(sales) AS sales
FROM category_sales
GROUP BY category
)
SELECT
category,
sales,
sales / NULLIF(SUM(sales) OVER (), 0) AS pct_of_total
FROM agg;

Tip: Pre-aggregate before dividing to avoid double counting.

Chandra Venkat
15) De-duplication (keep latest)

When to use:
Keep 1 row per email
Dedup CRM imports
Latest snapshot per key

Keep latest row per email

WITH r AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY updated_at DESC ) AS rn
FROM users_raw
)
SELECT * FROM r
WHERE rn = 1;

Tip: If “latest” is ambiguous, add tie-breakers (e.g., `updated_at, id DESC`).


Chandra Venkat
16) Pivot (rows → columns)

When to use:
Month columns per category
Status columns per team
One row per entity

Make months columns per category

SELECT
category,
SUM(CASE WHEN month='Jan' THEN sales ELSE 0 END) AS Jan_sales,
SUM(CASE WHEN month='Feb' THEN sales ELSE 0 END) AS Feb_sales,
SUM(CASE WHEN month='Mar' THEN sales ELSE 0 END) AS Mar_sales
FROM monthly_sales
GROUP BY category;

Tip: Keep column names short; too many pivoted columns hurt readability.
Chandra Venkat
17) Unpivot (cols → rows)

When to use:
Wide → long reshape
Report columns to rows
Normalize per-month fields

Turn Jan/Feb/Mar columns into rows

SELECT id, 'Jan' AS month, jan_sales AS sales FROM sales


UNION ALL
SELECT id, 'Feb', feb_sales FROM sales
UNION ALL
SELECT id, 'Mar', mar_sales FROM sales;

Tip: For many months, generate SQL via script or use a metadata table.
Chandra Venkat
18) Sessionization (gap > X)

When to use:
Break events into sessions
New session if gap > 30 min
Web/app analytics

Flag new session when gap > 30 min per user

SELECT
user_id, ts,
CASE
WHEN LAG(ts) OVER (PARTITION BY user_id ORDER BY ts) IS NULL
OR TIMESTAMPDIFF(
MINUTE,
LAG(ts) OVER (PARTITION BY user_id ORDER BY ts),
ts
) > 30
THEN 1 ELSE 0 END AS is_new_session FROM events;

Tip: Sum `is_new_session` over the partition to assign a session_id.


Chandra Venkat
19) Gaps / durations / inactivity

When to use:
Time between two timestamps
Idle time between events
SLAs and TATs

Days between ordered and delivered

SELECT
user_id,
order_id,
DATEDIFF(delivered_at, ordered_at) AS delivery_days
FROM orders
WHERE status = 'Delivered';

Tip: For hours/minutes, use `TIMESTAMPDIFF(unit, start, end)`

Chandra Venkat
20) Change detection across rows

When to use:
Detect status changes
Any value changed vs prior row
Emit change events

Emit rows where status changed

WITH x AS (
SELECT
ticket_id, status, ts,
LAG(status) OVER (PARTITION BY ticket_id ORDER BY ts) AS prev_status
FROM tickets
)
SELECT ticket_id, prev_status, status, ts
FROM x
WHERE (status <> prev_status)
OR (status IS NULL) <> (prev_status IS NULL);

Tip: Compare both inequality and NULL-ness for robust change detection.

Chandra Venkat
21) Streaks (islands & gaps)
When to use:
Consecutive daily logins
Runs of activity/inactivity
Start/end and length of streak
Compute login streaks per user
WITH a AS (
SELECT
user_id, login_date AS dt,
ROW_NUMBER() OVER ( PARTITION BY user_id
ORDER BY login_date ) AS rn
FROM user_logins_daily
),
b AS (
SELECT
user_id, DATE_SUB(dt, INTERVAL rn DAY) AS streak_key, dt
FROM a
)
SELECT
user_id,
MIN(dt) AS streak_start,
MAX(dt) AS streak_end,
COUNT(*) AS days_in_streak
FROM b
GROUP BY user_id, streak_key;

Tip: Ensure the input has one row per day per user (fill gaps first ifChandra
needed).
Venkat
22) Percentiles
When to use:
Tag into deciles
Pay banding by percentile

Assign deciles salary

SELECT
emp_id,
salary,
NTILE(10) OVER (ORDER BY salary DESC) AS decile
FROM employees;

Tip: Use `PERCENT_RANK()` for percentile thresholds without sorting twice.

Chandra Venkat
23) Event attribution (X before Y)
When to use:
“View before Purchase?”
First X vs first Y ordering
Basic path attribution

Users whose first View occurred before first Purchase

SELECT
user_id,
MIN(CASE WHEN event='View' THEN ts END) AS first_view,
MIN(CASE WHEN event='Purchase' THEN ts END) AS first_buy
FROM events
GROUP BY user_id
HAVING first_view IS NOT NULL
AND first_buy IS NOT NULL
AND first_view < first_buy;

Tip: Replace `MIN` with `MAX` for “last X before Y” style questions.

Chandra Venkat
24) String aggregation (CSV per entity)
When to use:
CSV of child values per parent
Tags per post
Items per order

CSV of products per user, newest first

SELECT
user_id,
GROUP_CONCAT(
product_name
ORDER BY purchase_time DESC
SEPARATOR ','
) AS products
FROM orders
GROUP BY user_id;

Tip: Add `DISTINCT` inside `GROUP_CONCAT(DISTINCT col ...)` to remove repeats.

Chandra Venkat
25) Recursive CTE (hierarchies / sequences)
When to use:
Org tree under a manager
Category hierarchies
Generate date/number sequences
Build org tree under manager id=100

-- employees(id, name, manager_id)


WITH RECURSIVE org AS (
-- anchor (root)
SELECT id, name, manager_id, 0 AS lvl
FROM employees
WHERE id = 100
UNION ALL
-- recursive step (children)
SELECT [Link], [Link], e.manager_id, [Link] + 1
FROM employees e
JOIN org o ON e.manager_id = [Link]
WHERE [Link] < 10 -- safety stop
)
SELECT *
FROM org
ORDER BY lvl, id;

Tip: Always include a stop condition (by level or count) to prevent runaway recursion.
Chandra Venkat
Want the full SQL Interview Kit (100+ questions)?
Built on these core SQL patterns.

Comment/DM SQL KIT - I’ll DM you free access.

Closes Tue, Sep 9, 2025 EOD

Follow for more practical data tips.


Chandra Venkat

You might also like