NULLIFReturn NULL when two expressions are equal; handy for safe division.revenue / NULLIF(quantity, 0)
CASTConvert to a target SQL type.CAST(total AS DECIMAL(12,2))
10
Window ranking
4 refs
Windows calculate across related rows without collapsing them like GROUP BY does.
ROW_NUMBERUnique sequence inside each partition; ties still receive different numbers.ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)
RANKTies share rank and leave gaps.RANK() OVER (ORDER BY total DESC)
DENSE_RANKTies share rank without gaps.DENSE_RANK() OVER (ORDER BY total DESC)
Top per groupRank inside a CTE/subquery, then filter the generated rank outside.WITH x AS (SELECT …, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, id DESC) rn FROM …) SELECT * FROM x WHERE rn = 1
11
Window analysis
5 refs
Running totals, comparisons with adjacent rows, and partition-level statistics.
Running sumAccumulate in deterministic order.SUM(total) OVER (PARTITION BY user_id ORDER BY created_at, id ROWS UNBOUNDED PRECEDING)
Partition totalAggregate per partition while retaining detail rows.SUM(total) OVER (PARTITION BY user_id)
LAGRead a previous row's value in window order.LAG(total) OVER (PARTITION BY user_id ORDER BY created_at)
LEADRead a following row's value.LEAD(created_at) OVER (PARTITION BY user_id ORDER BY created_at)
Moving avgExplicit ROWS frame avoids surprising peer/range behavior.AVG(total) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
12
INSERT, UPDATE, DELETE
6 refs
For destructive statements, prove the target rows with SELECT first and use transactions when appropriate.
INSERTInsert explicit columns; avoid relying on table column order.INSERT INTO users (id, email) VALUES (1, 'a@example.com')
INSERT … SELECTInsert a query result.INSERT INTO archive (id, email) SELECT id, email FROM users WHERE status = 'inactive'
UPDATEChange matching rows; verify the WHERE predicate first.UPDATE users SET status = 'inactive' WHERE last_login < '2025-01-01'
DELETERemove matching rows; missing WHERE means all rows.DELETE FROM sessions WHERE expires_at < CURRENT_TIMESTAMP
13
Tables & constraints
8 refs
Constraints make invalid states impossible at the database boundary instead of merely unlikely.
SQLiteInspect high-level query plan.EXPLAIN QUERY PLAN SELECT …
SQL ServerUse actual/estimated execution plans in SSMS or SET SHOWPLAN options.Inspect plan operators, estimates, scans/seeks
16
QA data checks
8 refs
Compact queries for validating uniqueness, completeness, referential integrity, and reconciliation.
DuplicatesFind keys that occur more than once.SELECT external_id, COUNT(*) c FROM t GROUP BY external_id HAVING COUNT(*) > 1
Missing requiredFind unexpected NULLs/blanks.SELECT * FROM t WHERE email IS NULL OR TRIM(email) = ''
OrphansFind children without a parent.SELECT c.* FROM child c LEFT JOIN parent p ON p.id = c.parent_id WHERE p.id IS NULL
Reconcile totalsCompare independently computed aggregates at the same grain.GROUP BY business_date, currency
Unexpected domainSurface values outside an allowed set.SELECT status, COUNT(*) FROM t GROUP BY status
FreshnessCheck latest expected event/load time.SELECT MAX(loaded_at) FROM warehouse_table
17
Limit & pagination by dialect
5 refs
Always pair pagination with deterministic ORDER BY. The row-limiting syntax is not uniform.
PostgreSQLLIMIT/OFFSET is common; FETCH is also supported.ORDER BY id LIMIT 20 OFFSET 40
MySQLLIMIT with OFFSET.ORDER BY id LIMIT 20 OFFSET 40
SQLiteLIMIT with OFFSET.ORDER BY id LIMIT 20 OFFSET 40
SQL ServerTOP for simple limits; OFFSET/FETCH for paging requires ORDER BY.ORDER BY id OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY
18
Upsert & changed rows
7 refs
Conflict handling and returning changed rows are vendor-specific—keep them isolated from portable SQL.
PostgreSQL upsertHandle a unique/exclusion conflict with ON CONFLICT.INSERT INTO users(id,email) VALUES (1,'a@x') ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email
SQLite upsertSQLite also supports ON CONFLICT upsert syntax.INSERT INTO users(id,email) VALUES (1,'a@x') ON CONFLICT(id) DO UPDATE SET email = excluded.email
MySQL upsertUse ON DUPLICATE KEY UPDATE for duplicate unique/primary keys.INSERT INTO users(id,email) VALUES (1,'a@x') ON DUPLICATE KEY UPDATE email = 'a@x'
PostgreSQL rowsRETURNING can return inserted/updated/deleted values.INSERT INTO users(email) VALUES ('a@x') RETURNING id
SQLite rowsModern SQLite supports RETURNING on top-level DML.UPDATE users SET active = 0 WHERE id = 1 RETURNING id
SQL Server rowsOUTPUT exposes inserted/deleted pseudo-tables.UPDATE users SET active = 0 OUTPUT inserted.id WHERE id = 1
19
Date/time by dialect
5 refs
Date arithmetic is one of the fastest places for otherwise-portable SQL to diverge.
SQLiteDate/time functions use modifier strings.date('now', '+7 days')
SQL ServerDATEADD / DATEDIFF family.DATEADD(day, 7, CAST(GETDATE() AS date))
Portable ideaStore/compare timestamps at a clear timezone/precision and test boundary conversions explicitly.UTC instant vs local business date are different concepts
20
String aggregation by dialect
4 refs
Basic string functions overlap, but aggregating many rows into one string uses different names.
PostgreSQLAggregate strings with a delimiter.STRING_AGG(name, ', ' ORDER BY name)
MySQLGROUP_CONCAT supports ordering/separators.GROUP_CONCAT(name ORDER BY name SEPARATOR ', ')
SQLitegroup_concat is the native name; current SQLite also documents string_agg as an alias.group_concat(name, ', ')
SQL ServerSTRING_AGG combines rows; ordering uses WITHIN GROUP.STRING_AGG(name, ', ') WITHIN GROUP (ORDER BY name)
21
Task · Find duplicates
4 refs
Interview prompt: find business-key values that occur more than once.
AnswerGroup by the key and keep only groups whose row count is greater than one.SELECT email, COUNT(*) AS copies
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
WhyGROUP BY creates one group per key; HAVING filters after aggregation.Use the real uniqueness rule: one column or the full composite business key.
22
Task · Return every duplicate row
2 refs
GROUP BY shows duplicate keys; this pattern returns the actual offending rows.
AnswerCount rows inside each key partition, then filter in an outer query.WITH x AS (
SELECT u.*, COUNT(*) OVER (PARTITION BY email) AS copies
FROM users u
)
SELECT * FROM x WHERE copies > 1;
Why outer query?Window values are produced after WHERE, so filter them one query level later.CTE / derived table → compute window result → outer WHERE
23
Task · Deduplicate safely
3 refs
Identify exactly which rows are duplicates before running dialect-specific DELETE syntax.
PreviewRank each duplicate group; rn = 1 is the survivor and rn > 1 are removal candidates.WITH ranked AS (
SELECT id, ROW_NUMBER() OVER (
PARTITION BY email ORDER BY updated_at DESC, id DESC
) AS rn
FROM users
)
SELECT id FROM ranked WHERE rn > 1;
Tie-breakerORDER BY must choose one deterministic survivor.ORDER BY updated_at DESC, id DESC
24
Task · Latest row per entity
2 refs
Return the newest order, event, status, or version for every entity.
AnswerNumber rows newest-first inside each entity and keep row 1.WITH ranked AS (
SELECT o.*, ROW_NUMBER() OVER (
PARTITION BY customer_id ORDER BY created_at DESC, id DESC
) AS rn
FROM orders o
)
SELECT * FROM ranked WHERE rn = 1;
PitfallMAX(created_at) alone returns only a timestamp and can match multiple tied rows.Add a stable secondary key such as id DESC.
25
Task · Second-highest distinct value
2 refs
Classic interview task: return the second distinct salary, score, or amount.
AnswerDENSE_RANK ranks equal values together and does not leave gaps between distinct values.WITH r AS (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS pos
FROM employees
)
SELECT DISTINCT salary FROM r WHERE pos = 2;
AlternativeFor exactly the second value, compare against the maximum.SELECT MAX(salary) FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);
26
Task · Nth-highest distinct value
2 refs
Generalize the second-highest problem while handling ties correctly.
AnswerRank distinct value groups and filter by the requested rank.WITH r AS (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS pos
FROM employees
)
SELECT DISTINCT salary FROM r WHERE pos = :n;
ROW_NUMBER?Use ROW_NUMBER only when every row should occupy a different position; use DENSE_RANK for distinct-value ranking.ties: ROW_NUMBER → separate positions · DENSE_RANK → same position
27
Task · Top N per group
2 refs
Return the top three products per category, employees per team, or scores per user.
AnswerPartition by the group, order by the metric, then filter the generated row number.WITH r AS (
SELECT p.*, ROW_NUMBER() OVER (
PARTITION BY category_id ORDER BY revenue DESC, id
) AS rn
FROM products p
)
SELECT * FROM r WHERE rn <= 3;
Include tiesUse RANK or DENSE_RANK when tied metric values should all qualify.RANK() OVER (PARTITION BY category_id ORDER BY revenue DESC)
28
Task · Find entities with no related rows
3 refs
Find customers without orders, users without profiles, or test cases without executions.
AnswerUse an anti-semi pattern with NOT EXISTS.SELECT c.*
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);
Why not NOT IN?NOT EXISTS avoids the NULL trap that can make NOT IN evaluate to unknown.Prefer NOT EXISTS when the subquery key may contain NULL.
29
Task · Find orphan child rows
2 refs
Data-quality task: locate child records whose referenced parent does not exist.
AnswerStart from the child table and reject rows that have a matching parent.SELECT o.*
FROM orders o
WHERE o.customer_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM customers c WHERE c.id = o.customer_id
);
QA useUseful after imports, disabled constraints, migrations, or ETL loads.Expected result in an integrity check: 0 rows.
30
Task · Compare expected vs actual data
3 refs
Find rows present in the expected result but missing or different in the actual result.
One directionEXCEPT returns distinct rows from the left result that are absent from the right result.SELECT id, status, amount FROM expected
EXCEPT
SELECT id, status, amount FROM actual;
Full diffRun EXCEPT in both directions to detect both missing and unexpected rows.expected EXCEPT actual
UNION ALL
actual EXCEPT expected
31
Task · Running total
2 refs
Calculate cumulative balance, revenue, defects, or executions without collapsing rows.
AnswerUse SUM as a window aggregate ordered by the sequence of events.SELECT account_id, occurred_at, amount,
SUM(amount) OVER (
PARTITION BY account_id
ORDER BY occurred_at, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM ledger;
Why ROWS?An explicit ROWS frame avoids peer-group surprises when several rows share the same ordering value.Use a deterministic ORDER BY, usually timestamp + primary key.
32
Task · Compare with the previous row
2 refs
Detect a change in status, price, count, or measurement between consecutive events.
AnswerLAG exposes a value from the previous row in the window ordering.WITH x AS (
SELECT e.*, LAG(status) OVER (
PARTITION BY entity_id ORDER BY occurred_at, id
) AS previous_status
FROM events e
)
SELECT * FROM x
WHERE previous_status IS NOT NULL
AND status <> previous_status;
First rowLAG returns NULL when no previous row exists unless a default is supplied.Handle the first row explicitly when NULL is also a valid business value.
AnswerCompare each sequence value with its predecessor.WITH x AS (
SELECT sequence_no,
LAG(sequence_no) OVER (ORDER BY sequence_no) AS prev_no
FROM events
)
SELECT prev_no + 1 AS gap_start, sequence_no - 1 AS gap_end
FROM x
WHERE sequence_no > prev_no + 1;
AssumptionThis checks numeric continuity, not whether every generated identifier must legally exist.Define the expected sequence rule before treating a gap as a defect.
34
Task · Count several conditions in one query
2 refs
Produce pass/fail/error counts or status KPIs without multiple scans or queries.
AnswerTurn each condition into 1 or 0 and aggregate it.SELECT
SUM(CASE WHEN status = 'passed' THEN 1 ELSE 0 END) AS passed,
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS errors
FROM test_results;
By groupAdd GROUP BY to calculate the same metrics per build, suite, team, or day.GROUP BY build_id
35
Task · No activity since a cutoff
2 refs
Find users or entities with no qualifying event in a given period without dialect-specific date arithmetic.
AnswerPass the cutoff as a parameter and use NOT EXISTS against qualifying activity.SELECT u.*
FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM events e
WHERE e.user_id = u.id
AND e.occurred_at >= :cutoff
);
Why parameter?Date-add/subtract syntax differs by dialect; computing the cutoff outside the query keeps the core pattern portable.:cutoff = application/test parameter
36
Task · Detect join multiplication
2 refs
Find parent rows that multiply after a join because the relationship is one-to-many or the join key is incomplete.
AnswerCount joined rows per expected parent key and inspect groups with more than one match.SELECT p.id, COUNT(*) AS joined_rows
FROM parents p
JOIN children c ON c.parent_id = p.id
GROUP BY p.id
HAVING COUNT(*) > 1;
InterpretationMultiple rows may be correct for 1:N; the defect is assuming 1:1 or omitting part of a composite join key.Check relationship cardinality before adding DISTINCT.
37
Task · Stable next-page query
3 refs
Use the last seen sort key instead of scanning an ever-growing OFFSET.
Core predicateContinue strictly after the last row using the same deterministic sort keys.WHERE created_at < :last_created_at
OR (created_at = :last_created_at AND id < :last_id)
ORDER BY created_at DESC, id DESC
Row capApply the dialect's normal row-limiting syntax after the predicate.PostgreSQL/MySQL/SQLite: LIMIT 50 · SQL Server: TOP / OFFSET-FETCH
38
Task · Percent of total
2 refs
Calculate each group's contribution while keeping one result row per group.
AnswerAggregate each group, then window-sum those aggregate results for the denominator.SELECT category_id,
SUM(amount) AS amount,
100.0 * SUM(amount)
/ NULLIF(SUM(SUM(amount)) OVER (), 0) AS pct_total
FROM sales
GROUP BY category_id;
Zero guardNULLIF prevents division by zero when the total is zero.NULLIF(total, 0)