You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-- 1. ROW_NUMBER: Find the 3rd transaction of every user (Uber) File: 30_Days_SQL_Challenge/05.sqlSELECT user_id, spend, transaction_date
FROM (
SELECT user_id, spend, transaction_date,
ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY transaction_date) AS rn
FROM transactions
) x1
WHERE rn =3;
-- 2. DENSE_RANK: Top 3 salary earners in each department (LeetCode) File: 30_Days_SQL_Challenge/09.sqlSELECT department_name, emp_name, salary
FROM (
SELECTd.nameAS department_name, e.nameAS emp_name, e.salary,
DENSE_RANK() OVER(PARTITION BY d.nameORDER BYe.salaryDESC) AS drn
FROM employee e
JOIN department d ONe.departmentId=d.id
) x1
WHERE drn <=3;
-- 3. RANK: First year each product was sold (Walmart) File: 30_Days_SQL_Challenge/29.sqlSELECT product_id, first_year, quantity, price
FROM (
SELECT product_id,
year AS first_year,
quantity,
price,
RANK() OVER(PARTITION BY product_id ORDER BY year) AS rn
FROM sales
) AS temp
WHERE rn =1;
Value & Analytics Functions
Function
Syntax
Purpose / Use Case
LAG(col, offset)
LAG(col) OVER(...)
Returns value from previous row (Period-over-Period comparisons)
LEAD(col, offset)
LEAD(col) OVER(...)
Returns value from next row
SUM() OVER()
SUM(col) OVER(...)
Running / Cumulative Total
AVG() OVER()
AVG(col) OVER(...)
Moving Average (using ROWS BETWEEN ...)
View Value/Analytics Examples
-- 1. LAG: Compare product revenue to previous year (Amazon) File: 30_Days_SQL_Challenge/05.sql
WITH prev_rev AS (
SELECT*,
LAG(revenue) OVER(PARTITION BY product_name ORDER BY year) AS prev_year_revenue
FROM product_revenue
)
SELECT product_name, revenue AS current_year_revenue, prev_year_revenue,
ROUND(((prev_year_revenue - revenue) / prev_year_revenue) *100.0, 2) AS pct_decrease
FROM prev_rev
WHERE prev_year_revenue > revenue;
-- 2. Moving Average: 3-day rolling average (General pattern)SELECT order_date, amount,
AVG(amount) OVER(
PARTITION BY user_id
ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3day
FROM orders;
-- 3. Running Total (Cumulative SUM): Revenue per product over time (Flipkart) File: 30_Days_SQL_Challenge/25.sqlSELECTdate,
product_id,
product_name,
revenue,
SUM(revenue) OVER (PARTITION BY product_id ORDER BYdate) AS running_total
FROM orders
ORDER BY product_id, date;
2. Date & Time Functions
Scenario
MySQL
PostgreSQL
SQLite
1. Extract Part from Date
EXTRACT(MONTH FROM date) YEAR(date), MONTH(date), DAY(date)
EXTRACT(MONTH FROM DATE '...') date_part('year', TIMESTAMP '...')
strftime('%m', date) CAST(strftime('%Y', date) AS INTEGER)
date(date, 'start of month') date(date, 'start of year')
8. TimeStamp Difference in Seconds
TIMESTAMPDIFF(SECOND, ts1, ts2)
EXTRACT(EPOCH FROM (ts2 - ts1))
strftime('%s', ts2) - strftime('%s', ts1)
View Comprehensive Date & Time Dialect Examples
1. Extracting Month/Year
-- MySQL / PostgreSQL (standard EXTRACT)SELECT EXTRACT(MONTH FROM submit_date) AS month, product_id, ROUND(AVG(stars), 2) AS avg_rating
FROM reviews
GROUP BY month, product_id;
-- SQLite (using strftime)SELECT strftime('%m', submit_date) AS month, product_id, ROUND(AVG(stars), 2) AS avg_rating
FROM reviews
GROUP BY month, product_id;
2. Cohort Purchase Differences (Within 30 Days)
-- MySQL
WITH ranked_orders AS (
SELECT user_id, order_date,
DENSE_RANK() OVER(PARTITION BY user_id ORDER BY order_date) AS rnk,
LAG(order_date) OVER(PARTITION BY user_id ORDER BY order_date) AS prev_order
FROM orders
)
SELECT*FROM ranked_orders
WHERE rnk =2AND DATEDIFF(order_date, prev_order) <=30;
-- PostgreSQL
WITH ranked_orders AS (
SELECT user_id, order_date,
DENSE_RANK() OVER(PARTITION BY user_id ORDER BY order_date) AS rnk,
LAG(order_date) OVER(PARTITION BY user_id ORDER BY order_date) AS prev_order
FROM orders
)
SELECT*FROM ranked_orders
WHERE rnk =2AND order_date - prev_order <=30;
-- SQLite
WITH ranked_orders AS (
SELECT user_id, order_date,
DENSE_RANK() OVER(PARTITION BY user_id ORDER BY order_date) AS rnk,
LAG(order_date) OVER(PARTITION BY user_id ORDER BY order_date) AS prev_order
FROM orders
)
SELECT*FROM ranked_orders
WHERE rnk =2AND julianday(order_date) - julianday(prev_order) <=30;
3. Aggregate & Conditional Functions
Function
Key Concept / Purpose
Key Rule
SUM / COUNT / AVG / MIN / MAX
Basic aggregations
Must pair with GROUP BY for non-aggregated columns
COUNT(DISTINCT col)
Count unique values
Ignores duplicates
HAVING
Filter on aggregated results
Evaluated afterGROUP BY (WHERE is evaluated before)
CASE WHEN
Conditional branching (if/elif/else)
Evaluated in order, returns first match
View Aggregate & Conditional Examples
-- 1. Pivot device types into columns (Facebook - 06.sql)SELECTSUM(CASE WHEN device_type ='laptop' THEN viewership_count ELSE 0 END) AS laptop_views,
SUM(CASE WHEN device_type IN ('tablet', 'phone') THEN viewership_count ELSE 0 END) AS mobile_views
FROM viewership;
-- 2. Companies with duplicate job listings (LinkedIn - 14.sql)SELECT company_id, title, COUNT(1) AS total_job
FROM job_listings
GROUP BY1, 2HAVINGCOUNT(1) >1;
4. NULL Handling & Math Functions
Function / Technique
Syntax / Pattern
Purpose
COALESCE()
COALESCE(possibly_null, fallback)
Returns first non-NULL value in list
IS NULL / IS NOT NULL
col IS NULL
Check if a column has missing (NULL) values
ROUND()
ROUND(value, decimal_places)
Formats numeric outputs
ABS()
ABS(value)
Absolute value (great for differences/variances)
Float Cast
col * 1.0 or col::float
Avoids integer division truncation
View NULL & Math Examples
-- 1. Default NULL end_date to today for duration check (IBM - 12.sql)SELECT department,
AVG(end_date - COALESCE(start_date, CURRENT_DATE)) AS avg_duration
FROM employee_service
GROUP BY1;
-- 2. Avoid integer division truncation (Amazon - 26.sql)SELECT ROUND((returned_items *1.0/ total_items_ordered) *100, 2) AS return_pct
FROM order_summary;
5. String & Formatting Functions
Function
Purpose
Example
Result
UPPER(col) / LOWER(col)
Standardize case
UPPER('sql')
'SQL'
TRIM(col)
Remove leading/trailing spaces
TRIM(' hi ')
'hi'
LENGTH(col)
Count characters
LENGTH('SQL')
3
SUBSTRING(col, start, len)
Extract slice
SUBSTRING('hello', 1, 3)
'hel'
CONCAT(a, b)
Join strings
CONCAT('first', ' ', 'last')
'first last'
LIKE / ILIKE
Pattern match (% wildcard)
WHERE col ILIKE 'A%'
Case-insensitive starts with A
6. CTEs & Subquery Patterns
Common Table Expression (CTE) Template
WITH first_step AS (
SELECT user_id, COUNT(order_id) AS orders_count FROM orders GROUP BY1
),
second_step AS (
SELECT user_id, orders_count, DENSE_RANK() OVER(ORDER BY orders_count DESC) AS rnk FROM first_step
)
SELECT*FROM second_step WHERE rnk =1;
Essential Subquery Patterns
Anti-Join:WHERE col NOT IN (SELECT DISTINCT col FROM table)
Derived Table:SELECT * FROM (SELECT ... ) x1
Scalar Lookup:WHERE spend = (SELECT MAX(spend) FROM table)
View CTE & Subquery Examples
-- Amazon - 07.sql: Top 2 products per category using chained CTEs
WITH total_trans AS (
SELECT category, product, SUM(spend) AS total_spend
FROM product_spend
WHERE EXTRACT(YEAR FROM transaction_date) =2022GROUP BY1, 2
),
high_sell_prod AS (
SELECT*,
ROW_NUMBER() OVER(PARTITION BY category ORDER BY total_spend DESC) AS rw_nm
FROM total_trans
)
SELECT category, product, total_spend
FROM high_sell_prod
WHERE rw_nm <=2;
7. Join Patterns
Anti-Join (LEFT JOIN ... WHERE B.col IS NULL): Find non-matching records (e.g. users who never purchased).
Self-Join (JOIN a table to itself): Query hierarchies (manager/employee) or compare sequential rows.
Cross-Join (CROSS JOIN): Cartesian product (all combinations) for matrix generation.
View Join Examples
-- 1. Anti-Join via NOT IN: Pages with zero likes (Facebook) File: 30_Days_SQL_Challenge/02.sqlSELECTp.page_idFROM pages p
WHEREp.page_id NOT IN (SELECT DISTINCT page_id FROM page_likes);
-- 2. Anti-Join via LEFT JOIN: Pages with zero likes (Facebook) File: 30_Days_SQL_Challenge/02.sqlSELECTp.page_idFROM pages p
LEFT JOIN page_likes pl ONp.page_id=pl.page_idWHEREpl.page_id IS NULL;
-- 3. Self-Join via LEFT JOIN: Manager-Employee hierarchy (TCS) File: 30_Days_SQL_Challenge/22.sqlSELECTe1.emp_id, e1.emp_name, e2.emp_nameAS manager_name
FROM employees e1
LEFT JOIN employees e2 ONe1.manager_id=e2.emp_id;
-- 4. CROSS JOIN: Manager-Employee lookup (TCS) File: 30_Days_SQL_Challenge/22.sql-- CROSS JOIN produces a Cartesian product. When filtered with WHERE, it acts like an INNER JOIN.-- Useful when you want every combination and then narrow down with a condition.SELECTe1.emp_id, e1.emp_name, e1.manager_id, e2.emp_nameAS manager_name
FROM employees AS e1
CROSS JOIN employees AS e2
WHEREe1.manager_id=e2.emp_id;
-- 5. Running Total Self-Join (alternative to window function) (Flipkart) File: 30_Days_SQL_Challenge/25.sqlSELECTo1.date, o1.product_id, o1.product_name, o1.revenue,
SUM(o2.revenue) AS running_total
FROM orders AS o1
JOIN orders AS o2
ONo1.product_id=o2.product_idANDo1.date>=o2.dateGROUP BYo1.date, o1.product_id, o1.product_name, o1.revenueORDER BY1, 2;
8. Median — Classic Interview Pattern
WITH ranked_cte AS (
SELECT views,
ROW_NUMBER() OVER(ORDER BY views ASC) AS rn_asc,
ROW_NUMBER() OVER(ORDER BY views DESC) AS rn_desc
FROM tiktok
)
SELECTAVG(views) AS median
FROM ranked_cte
WHERE ABS(rn_asc - rn_desc) <=1;
9. LIMIT / TOP / FETCH FIRST — Restricting Result Rows
Dialect
Syntax
Notes
MySQL
SELECT ... LIMIT n;
Most common shorthand
MySQL (offset)
SELECT ... LIMIT offset, n;
Skip offset rows, return n
PostgreSQL
SELECT ... LIMIT n;
Same as MySQL
PostgreSQL (standard)
SELECT ... FETCH FIRST n ROWS ONLY;
SQL standard syntax
SQLite
SELECT ... LIMIT n;
Same as MySQL
SQL Server
SELECT TOP n ...;
Goes before column list, not at end
Oracle
WHERE ROWNUM <= n
Filter-based, must wrap sorted subquery
View LIMIT / TOP Examples
-- 1. MySQL / PostgreSQL / SQLite — Top 5 products by revenue decrease (Amazon) File: 30_Days_SQL_Challenge/05.sqlSELECT product_name, revenue_decreased, rev_decreased_ratio
FROM rev_comp
WHERE revenue_decreased >0LIMIT5;
-- 2. PostgreSQL — Top 5 customers by return percentage (Amazon) File: 30_Days_SQL_Challenge/26.sqlSELECT customer_id, return_percentage
FROM result_cte
ORDER BY return_percentage DESCLIMIT5;
-- 3. MySQL / PostgreSQL / SQLite — Top 5 songs by listen count (Spotify) File: 30_Days_SQL_Challenge/30.sqlSELECT song_name, times_of_listens
FROM (
SELECTs.song_name, COUNT(l.listen_id) AS times_of_listens
FROM Songs s
JOIN Listens l ONs.song_id=l.song_idGROUP BYs.song_name
) AS sub
ORDER BY times_of_listens DESCLIMIT5;
-- 4. PostgreSQL — FETCH FIRST (SQL standard equivalent of LIMIT) File: 30_Days_SQL_Challenge/28.sqlSELECT seller_id, total_sales, total_return_qty
FROM result_cte
ORDER BY total_sales DESC, total_return_qty ASC
FETCH FIRST 3 ROWS ONLY;
-- 5. SQL Server equivalent (TOP goes at the start)SELECT TOP 5 song_name, times_of_listens
FROM sub
ORDER BY times_of_listens DESC;
10. CAST & Type Casting
Dialect Comparison
Dialect
Syntax
Use Case
Standard SQL
CAST(col AS type)
Universal — works in all dialects
PostgreSQL shorthand
col::type
Cleaner syntax, PostgreSQL-only
MySQL
CAST(col AS DECIMAL) or col * 1.0
No :: shorthand
SQLite
CAST(col AS REAL) or col * 1.0
No :: shorthand
Common Target Types
Type
Purpose
FLOAT / REAL
Force decimal division (avoid integer truncation)
NUMERIC(p, s)
Precise decimal with precision and scale
VARCHAR / TEXT
Convert number/date to string
DATE
Convert string literal to a date
INTEGER
Truncate float to whole number
View CAST / Type Casting Examples
-- 1. PostgreSQL :: shorthand — Cast string literal to DATE for date arithmetic (Flipkart) File: 30_Days_SQL_Challenge/15.sqlWHERE EXTRACT(MONTH FROM saledate) = EXTRACT(MONTH FROM'2024-03-01'::DATE) -1-- 2. PostgreSQL :: shorthand — Cast to FLOAT to avoid integer division (Amazon) File: 30_Days_SQL_Challenge/26.sql
ROUND(
CASE
WHEN total_items_ordered >0
THEN (total_items_returned::FLOAT / total_items_ordered::FLOAT) *100
ELSE 0
END::NUMERIC, 2
) AS return_percentage
-- 3. Multiply by 1.0 — Universal float cast trick (Amazon) File: 30_Days_SQL_Challenge/26.sql
ROUND((returned_items *1.0/ total_items_ordered) *100, 2) AS return_pct
-- 4. Standard CAST() — Convert string to integer for year extraction (SQLite) File: General SQLite pattern for strftime resultsSELECT CAST(strftime('%Y', '2026-05-22') ASINTEGER) AS year_int;
-- 5. CAST to NUMERIC for rounding — PostgreSQL File: 30_Days_SQL_Challenge/26.sql
ROUND((some_float_result)::NUMERIC, 2)
-- Dialect Quick Reference:-- MySQL/SQLite: col * 1.0 OR CAST(col AS DECIMAL(10,2))-- PostgreSQL: col::float OR CAST(col AS FLOAT)-- SQL Server: CAST(col AS FLOAT) OR CONVERT(FLOAT, col)
11. Advanced Window Functions
Distribution & Navigation Functions
Function
Syntax
Purpose
NTILE(n)
NTILE(4) OVER(ORDER BY salary DESC)
Splits rows into n equal buckets (great for quartiles/percentiles)
PERCENT_RANK()
PERCENT_RANK() OVER(ORDER BY score)
Relative rank as a fraction (rank-1)/(total_rows-1) — range [0,1]
CUME_DIST()
CUME_DIST() OVER(ORDER BY score)
Cumulative distribution — fraction of rows ≤ current row
FIRST_VALUE(col)
FIRST_VALUE(col) OVER(PARTITION BY ... ORDER BY ...)
Returns first value in the window frame
LAST_VALUE(col)
LAST_VALUE(col) OVER(... ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
Returns last value in the window frame
NTH_VALUE(col, n)
NTH_VALUE(col, 2) OVER(...)
Returns the nth value in the window
View Advanced Window Function Examples
-- 1. NTILE: Label employees by salary quartileSELECT emp_name, salary,
NTILE(4) OVER(ORDER BY salary DESC) AS salary_quartile
FROM employees;
-- Quartile 1 = top 25%, Quartile 4 = bottom 25%-- 2. PERCENT_RANK: What % of products have lower sales than this one?SELECT product_id, total_sales,
ROUND(PERCENT_RANK() OVER(ORDER BY total_sales) *100, 2) AS pct_rank
FROM product_summary;
-- 3. CUME_DIST: Find top 30% of customers by spendSELECT customer_id, total_spend
FROM (
SELECT customer_id, total_spend,
CUME_DIST() OVER(ORDER BY total_spend DESC) AS cum_dist
FROM customer_summary
) x
WHERE cum_dist <=0.30;
-- 4. FIRST_VALUE / LAST_VALUE: Compare each row to the department's highest/lowest salarySELECT emp_name, department, salary,
FIRST_VALUE(salary) OVER(PARTITION BY department ORDER BY salary DESC) AS dept_max_salary,
LAST_VALUE(salary) OVER(PARTITION BY department ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS dept_min_salary
FROM employees;
-- NOTE: LAST_VALUE needs explicit frame to include all rows (default frame stops at current row)-- 5. NTH_VALUE: Get the 2nd highest salary per departmentSELECT emp_name, department, salary,
NTH_VALUE(salary, 2) OVER(PARTITION BY department ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS second_highest
FROM employees;
12. NULL Safety & Conditional Functions
Function
Syntax
Purpose
Note
NULLIF(a, b)
NULLIF(denominator, 0)
Returns NULL if a = b, else returns a
Critical to prevent division-by-zero errors
IFNULL(col, default)
IFNULL(score, 0)
Returns default if col IS NULL
MySQL / SQLite only
ISNULL(col, default)
ISNULL(score, 0)
Same as IFNULL
SQL Server only
NVL(col, default)
NVL(score, 0)
Same as IFNULL
Oracle only
COALESCE(a, b, c, ...)
COALESCE(col1, col2, 0)
Returns first non-NULL from the list
Universally supported — preferred
IIF(cond, true_val, false_val)
IIF(score > 90, 'A', 'B')
Inline if/else shorthand
SQL Server / Access only
View NULL Safety & Conditional Examples
-- 1. NULLIF: Avoid division by zero (universal pattern)SELECT product_id,
total_revenue / NULLIF(total_units_sold, 0) AS avg_revenue_per_unit
FROM product_summary;
-- If total_units_sold = 0, the denominator becomes NULL → result is NULL (safe, no error)-- 2. COALESCE vs IFNULL (behavior is identical for 2 arguments):SELECT emp_name,
COALESCE(bonus, 0) AS bonus_coalesce, -- Works in ALL dialects
IFNULL(bonus, 0) AS bonus_ifnull -- MySQL / SQLite onlyFROM employees;
-- 3. COALESCE with multiple fallbacks (COALESCE wins here — IFNULL only takes 2 args):SELECT COALESCE(preferred_email, work_email, personal_email, 'no-email@unknown.com') AS contact_email
FROM users;
-- 4. Combined pattern: NULLIF + ROUND + COALESCE (production-grade safe division)SELECT
ROUND(COALESCE(num_clicks *1.0/ NULLIF(num_impressions, 0), 0) *100, 2) AS ctr_pct
FROM ad_metrics;
13. Advanced String Functions
Function
MySQL
PostgreSQL
SQL Server
Purpose
LEFT(col, n)
LEFT(col, 3)
LEFT(col, 3)
LEFT(col, 3)
First n characters
RIGHT(col, n)
RIGHT(col, 3)
RIGHT(col, 3)
RIGHT(col, 3)
Last n characters
REPLACE(col, old, new)
REPLACE(col,'a','b')
REPLACE(col,'a','b')
REPLACE(col,'a','b')
Substitute substring
POSITION(sub IN col)
POSITION('x' IN col)
POSITION('x' IN col)
CHARINDEX('x', col)
Index of first occurrence
SPLIT_PART(col, delim, n)
SUBSTRING_INDEX(col,',',1)
SPLIT_PART(col,',',1)
(use STRING_SPLIT)
Extract nth split token
REGEXP_LIKE(col, pattern)
col REGEXP 'pattern'
col ~ 'pattern'
col LIKE '%pattern%'*
Regex pattern match
LPAD / RPAD
LPAD(col, 5, '0')
LPAD(col, 5, '0')
RIGHT('00000'+col, 5)
Pad string to fixed width
REVERSE(col)
REVERSE(col)
REVERSE(col)
REVERSE(col)
Reverse a string
View Advanced String Function Examples
-- 1. LEFT / RIGHT: Extract area code and last 4 digits from phone numberSELECT phone_number,
LEFT(phone_number, 3) AS area_code,
RIGHT(phone_number, 4) AS last_four
FROM customers;
-- 2. REPLACE: Sanitize data by removing unwanted charactersSELECT REPLACE(REPLACE(phone_number, '-', ''), '', '') AS clean_phone
FROM customers;
-- 3. POSITION (PostgreSQL/MySQL) vs CHARINDEX (SQL Server):-- Find the position of '@' in an email to extract the domain-- PostgreSQL / MySQL:SELECT email, SUBSTRING(email, POSITION('@'IN email) +1) AS domain FROM users;
-- SQL Server:SELECT email, SUBSTRING(email, CHARINDEX('@', email) +1, LEN(email)) AS domain FROM users;
-- 4. SPLIT_PART: Extract first name from a full name (PostgreSQL)SELECT SPLIT_PART(full_name, '', 1) AS first_name FROM employees;
-- MySQL equivalent using SUBSTRING_INDEX:SELECT SUBSTRING_INDEX(full_name, '', 1) AS first_name FROM employees;
-- 5. REGEXP: Find rows where email doesn't match a valid pattern (MySQL)SELECT*FROM users WHERE email NOT REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
-- PostgreSQL equivalent using ~ (tilde):SELECT*FROM users WHERE email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
-- 6. LPAD: Zero-pad an ID to always be 6 digits wideSELECT LPAD(CAST(user_id ASVARCHAR), 6, '0') AS padded_id FROM users;
14. GROUP BY Extensions (ROLLUP, CUBE, GROUPING SETS)
Clause
Purpose
Typical Use Case
GROUP BY ROLLUP(a, b)
Hierarchical subtotals: (a,b), (a), ()
Sales totals by region → country → grand total
GROUP BY CUBE(a, b)
All possible subtotal combinations
Cross-dimensional reporting (every combo of dims)
GROUP BY GROUPING SETS((a,b),(a),(b),())
Explicit control over which groupings to include
Custom multi-level aggregation
GROUPING(col)
Returns 1 if the column is aggregated (NULL represents subtotal), 0 otherwise
Distinguish real NULLs from rollup NULLs
View GROUP BY Extension Examples
-- 1. ROLLUP: Sales by (region, product), then just (region), then grand totalSELECT region, product, SUM(sales) AS total_sales
FROM orders
GROUP BY ROLLUP(region, product)
ORDER BY region, product;
-- Produces rows for each (region, product) pair + a subtotal per region + a grand total row-- 2. CUBE: Every combination of (region) and (year)SELECT region, year, SUM(revenue) AS total_revenue
FROM sales
GROUP BY CUBE(region, year);
-- Produces: (region,year), (region), (year), () — all 4 combinations-- 3. GROUPING SETS: Explicitly pick only the groupings you needSELECT region, product_category, SUM(revenue) AS total_revenue
FROM sales
GROUP BY GROUPING SETS (
(region, product_category), -- by both dimensions
(region), -- by region only
() -- grand total only
);
-- 4. GROUPING(): Differentiate real NULLs from subtotal NULLsSELECT
CASE WHEN GROUPING(region) =1 THEN 'ALL REGIONS' ELSE region END AS region_label,
SUM(sales) AS total_sales
FROM orders
GROUP BY ROLLUP(region);
15. Set Operations (UNION, INTERSECT, EXCEPT)
Operator
Behavior
Duplicates
UNION
Combines results of two queries
Removes duplicates (like DISTINCT)
UNION ALL
Combines results of two queries
Keeps all rows including duplicates — faster than UNION
INTERSECT
Returns rows present in both queries
Removes duplicates
EXCEPT (MINUS in Oracle)
Returns rows in first query not in second
Removes duplicates
Key Rules:
Both queries must have the same number of columns and compatible data types
Column names come from the first query
ORDER BY goes at the very end (applies to the full combined result)
View Set Operation Examples
-- 1. UNION: Combine customers from two regions (deduplicating overlaps)SELECT customer_id, name FROM customers_us
UNIONSELECT customer_id, name FROM customers_eu;
-- 2. UNION ALL: Combine all transactions from two tables (keep duplicates, faster)SELECT order_id, amount, 'online'AS channel FROM online_orders
UNION ALLSELECT order_id, amount, 'in_store'AS channel FROM store_orders;
-- 3. INTERSECT: Find customers who bought in BOTH last month AND this monthSELECT customer_id FROM orders WHERE EXTRACT(MONTH FROM order_date) =5
INTERSECT
SELECT customer_id FROM orders WHERE EXTRACT(MONTH FROM order_date) =6;
-- 4. EXCEPT: Find customers who bought last month but NOT this month (churned)SELECT customer_id FROM orders WHERE EXTRACT(MONTH FROM order_date) =5
EXCEPT
SELECT customer_id FROM orders WHERE EXTRACT(MONTH FROM order_date) =6;
-- Oracle equivalent uses MINUS instead of EXCEPT:-- SELECT customer_id FROM last_month_orders-- MINUS-- SELECT customer_id FROM this_month_orders;-- 5. Classic interview pattern: Symmetric difference using UNION ALL + EXCEPT-- (rows in A not in B, OR rows in B not in A)
(SELECT customer_id FROM table_a EXCEPT SELECT customer_id FROM table_b)
UNION ALL
(SELECT customer_id FROM table_b EXCEPT SELECT customer_id FROM table_a);
16. EXISTS & NOT EXISTS
Pattern
Syntax
vs. Alternative
EXISTS
WHERE EXISTS (SELECT 1 FROM ...)
Faster than IN when subquery result is large; short-circuits on first match
NOT EXISTS
WHERE NOT EXISTS (SELECT 1 FROM ...)
Safer than NOT IN — handles NULLs correctly
Critical Gotcha:NOT IN returns no rows if the subquery contains any NULL. NOT EXISTS handles NULLs safely and is almost always the better choice.
View EXISTS / NOT EXISTS Examples
-- 1. EXISTS: Find customers who placed at least one orderSELECTc.customer_id, c.nameFROM customers c
WHERE EXISTS (
SELECT1FROM orders o WHEREo.customer_id=c.customer_id
);
-- 2. NOT EXISTS: Find customers who have NEVER placed an order (safe NULL handling)SELECTc.customer_id, c.nameFROM customers c
WHERE NOT EXISTS (
SELECT1FROM orders o WHEREo.customer_id=c.customer_id
);
-- Preferred over NOT IN when subquery might return NULLs-- 3. NOT IN pitfall (DANGEROUS if subquery has NULLs):SELECT customer_id FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM orders);
-- If any customer_id in orders is NULL, this returns 0 rows!-- 4. EXISTS with correlated subquery: Products that were sold in every regionSELECTp.product_id, p.product_nameFROM products p
WHERE NOT EXISTS (
SELECT1FROM regions r
WHERE NOT EXISTS (
SELECT1FROM sales s
WHEREs.product_id=p.product_idANDs.region_id=r.region_id
)
);
Collapse many rows into one comma-separated string
Aggregate to array
(not native)
ARRAY_AGG(col ORDER BY col)
(not native)
Collapse rows into a SQL array
Count distinct
COUNT(DISTINCT col)
COUNT(DISTINCT col)
COUNT(DISTINCT col)
Count unique values — already in Section 3
View Collection Aggregation Examples
-- 1. STRING_AGG (PostgreSQL / SQL Server): List all skills per employee as a stringSELECT employee_id,
STRING_AGG(skill, ', 'ORDER BY skill) AS all_skills
FROM employee_skills
GROUP BY employee_id;
-- Result: emp_id=1 → "Excel, Python, SQL"-- 2. GROUP_CONCAT (MySQL): Same patternSELECT employee_id,
GROUP_CONCAT(skill ORDER BY skill SEPARATOR ', ') AS all_skills
FROM employee_skills
GROUP BY employee_id;
-- 3. ARRAY_AGG (PostgreSQL): Aggregate into a true array for array operationsSELECT department_id,
ARRAY_AGG(emp_name ORDER BY salary DESC) AS employees_by_salary
FROM employees
GROUP BY department_id;
-- 4. Interview Pattern: Find employees who hold multiple roles (using GROUP_CONCAT / STRING_AGG)SELECT employee_id,
COUNT(role) AS num_roles,
STRING_AGG(role, ', ') AS roles_held
FROM employee_roles
GROUP BY employee_id
HAVINGCOUNT(role) >1;
-- 5. De-duplicate and join values (PostgreSQL)SELECT department_id,
STRING_AGG(DISTINCT emp_name, ', 'ORDER BY emp_name) AS unique_employees
FROM assignments
GROUP BY department_id;
18. Statistical & Percentile Functions
Function
Syntax
Purpose
Support
STDDEV(col)
STDDEV(salary)
Population standard deviation
MySQL, PostgreSQL, SQL Server (STDEV)
VARIANCE(col)
VARIANCE(salary)
Population variance
MySQL, PostgreSQL, SQL Server (VAR)
STDDEV_SAMP(col)
STDDEV_SAMP(salary)
Sample standard deviation (Bessel's correction)
MySQL, PostgreSQL
PERCENTILE_CONT(p)
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
Continuous percentile (interpolates) — median
PostgreSQL, SQL Server
PERCENTILE_DISC(p)
PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY salary)
Discrete percentile (returns an actual row value)
PostgreSQL, SQL Server
CORR(x, y)
CORR(price, units_sold)
Pearson correlation coefficient
PostgreSQL
REGR_SLOPE(y, x)
REGR_SLOPE(revenue, units)
Slope of linear regression line
PostgreSQL
View Statistical Function Examples
-- 1. STDDEV & VARIANCE: Measure salary spread by departmentSELECT department,
ROUND(AVG(salary), 2) AS avg_salary,
ROUND(STDDEV(salary), 2) AS salary_stddev,
ROUND(VARIANCE(salary), 2) AS salary_variance
FROM employees
GROUP BY department;
-- 2. PERCENTILE_CONT: Compute median salary (continuous interpolation) — PostgreSQL / SQL ServerSELECT department,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary
FROM employees
GROUP BY department;
-- 3. PERCENTILE_DISC: Compute 75th percentile (returns an actual observed salary value)SELECT PERCENTILE_DISC(0.75) WITHIN GROUP (ORDER BY salary) AS p75_salary
FROM employees;
-- 4. MySQL Median workaround (no PERCENTILE_CONT):-- Use the ROW_NUMBER dual-rank trick (see Section 8 for full pattern)
WITH ranked AS (
SELECT salary,
ROW_NUMBER() OVER (ORDER BY salary ASC) AS rn_asc,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn_desc
FROM employees
)
SELECTAVG(salary) AS median_salary
FROM ranked
WHERE ABS(rn_asc - rn_desc) <=1;
-- 5. CORR: Correlation between price and quantity sold (PostgreSQL)SELECT ROUND(CORR(price, units_sold)::NUMERIC, 4) AS price_units_correlation
FROM product_sales;
-- Result near +1: strong positive correlation; near -1: inverse; near 0: no linear relationship-- 6. STDDEV as a window function (detect outliers)SELECT emp_name, salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg,
STDDEV(salary) OVER (PARTITION BY department) AS dept_stddev
FROM employees;
-- Outlier if: ABS(salary - dept_avg) > 2 * dept_stddev
19. QUALIFY (Snowflake / BigQuery)
QUALIFY is a Snowflake/BigQuery-specific clause that filters the results of window functions directly, without needing a subquery or CTE. It is evaluated after WHERE, GROUP BY, and HAVING.
-- Standard approach (any dialect) — requires a subquery:SELECT user_id, spend, transaction_date
FROM (
SELECT user_id, spend, transaction_date,
ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY transaction_date) AS rn
FROM transactions
) x
WHERE rn =3;
-- Snowflake / BigQuery shorthand using QUALIFY (no subquery needed):SELECT user_id, spend, transaction_date
FROM transactions
QUALIFY ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY transaction_date) =3;