What is the difference between WHERE and HAVING?
WHERE filters rows before grouping, HAVING filters groups after aggregation. The practical consequence: an aggregate like SUM can only be filtered in HAVING, and pushing every possible predicate into WHERE first is both correct and cheaper, because it shrinks the data before the group step.
RANK vs DENSE_RANK vs ROW_NUMBER?
All 3 number rows within a window. ROW_NUMBER forces unique positions, arbitrarily on ties unless you add a tiebreaker. RANK gives ties the same position and skips the next numbers. DENSE_RANK gives ties the same position and skips nothing. Top-N answers change depending on which you pick, which is why interviewers ask.
Why does NOT IN break on NULLs and NOT EXISTS does not?
NOT IN compiles to a chain of not-equals comparisons, and any comparison with NULL is unknown, so one NULL in the subquery makes every row fail the predicate and the query silently returns nothing. NOT EXISTS just checks for the presence of a matching row, so NULLs in the compared column never poison it.
UNION vs UNION ALL?
UNION deduplicates the combined result, which forces a sort or hash across the whole set. UNION ALL just concatenates. Default to UNION ALL unless you have a stated reason to dedupe: it is semantically explicit and avoids a hidden performance cliff on large sets.
What does COUNT(column) do that COUNT(*) does not?
COUNT(*) counts rows. COUNT(column) counts rows where that column is not NULL, and COUNT(DISTINCT column) counts distinct non-NULL values. The gap between COUNT(*) and COUNT(column) is a quick null-rate probe, a trick worth mentioning in a data-quality discussion.
When would you use a CTE over a subquery, and when a temp table?
A CTE buys readability and reuse within one statement, and modern planners usually inline it. A temp table materializes: worth it when the intermediate result is reused across statements, needs an index, or the planner keeps re-executing an expensive subquery. Naming that materialization tradeoff is the senior version of this answer.
ROWS vs RANGE in a window frame?
ROWS counts physical rows; RANGE groups peer rows with equal ORDER BY values and includes them together. The default frame with an ORDER BY is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, so a rolling average written without an explicit ROWS clause is a running average with tie-merging. Always write the frame you mean.
What is a correlated subquery and what does it cost?
A subquery that references the outer row, so it conceptually re-executes per row. Planners often rewrite it as a join, but when they cannot, an O(n) query becomes O(n squared). The interview follow-up is usually to rewrite one as a window function or join, so practice both directions.
Why did my JOIN double my revenue numbers?
A many-to-many join. If the join key is not unique on either side, rows fan out and every aggregate downstream inflates. The fix is to aggregate or dedupe to the correct grain before joining. Saying the word grain out loud is half the answer.
How do you find and remove duplicate rows?
ROW_NUMBER over PARTITION BY the natural key, ordered by the survivorship rule, then keep rn = 1. For detection alone, GROUP BY the key with HAVING COUNT(*) > 1. The interviewer usually pushes on which row survives, which is a business rule, not a SQL detail, and noticing that is the point.
What is the gaps and islands problem and how do you solve it?
Finding runs of consecutive values: consecutive login days, unbroken subscription months, contiguous active periods. The standard solution differences a row number against the sequence itself, so ROW_NUMBER() OVER (PARTITION BY user ORDER BY day) subtracted from the date yields a constant per streak. Group by that constant and each group is one island. Derive it once rather than memorizing it, because the tiebreaker and the gap tolerance change per prompt.
When would you use a recursive CTE instead of a join?
When the depth is unknown at write time: walking a manager hierarchy of arbitrary height, exploding a bill of materials, or generating a date spine. A recursive CTE has an anchor member and a recursive member joined back to the CTE, and it terminates when the recursive member returns no rows. A fixed number of self-joins is the right answer when the depth is known and small; recursion is for when it is not.
What is the difference between a star schema and a snowflake schema?
Both put a fact table at the center. A star schema keeps dimensions denormalized in one table each, so queries join once per dimension. A snowflake schema normalizes dimensions into sub-tables, saving storage and enforcing integrity at the cost of extra joins. OLAP analytical warehouses usually favor the star: fewer joins, simpler queries, and storage is cheap relative to query time.
How do you implement a Slowly Changing Dimension Type 2 in SQL?
Instead of overwriting a changed attribute, insert a new row and close the old one. Each row carries valid_from, valid_to, and an is_current flag; the update sets the prior row's valid_to to the change timestamp and flips is_current to false. Point-in-time queries then join on the fact's event date falling between valid_from and valid_to. SCD Type 1 overwrites and keeps no history, which is the contrast interviewers want stated.
How do you make an incremental SQL pipeline idempotent?
Idempotency means re-running the same load produces the same result rather than duplicating rows. In SQL that is usually a MERGE keyed on a business key, or a delete-then-insert scoped to the partition being rebuilt, both wrapped in one transaction. The anti-pattern is a bare INSERT on a retry path: the first partial run leaves rows behind and the retry doubles them. Deterministic partition boundaries matter as much as the write itself.
How do you read an EXPLAIN plan to find a slow query?
Read it inside out, starting at the leaves. Look for sequential scans on large tables where an index exists, nested loops driven by a big outer relation, and a large gap between estimated and actual row counts, which signals stale statistics. EXPLAIN ANALYZE runs the query and reports real timings per node, so the node consuming the most actual time is the one to fix. Add the index or rewrite the join order, then re-read the plan.
What is partition pruning and when does it fail?
Partition pruning is the planner skipping partitions that cannot match the query's predicate, so a filter on the partition key reads one day instead of five years. It fails when the predicate wraps the partition column in a function, compares it against a non-constant the planner cannot resolve, or uses a type that forces an implicit cast. Filter on the raw partition column with a literal or a bound parameter and pruning holds.
What is data skew and how do you handle it in a distributed JOIN?
Skew is one join key holding a disproportionate share of rows, so a single task processes most of the data while the rest idle and the job stalls at 99%. Detect it by counting rows per key on both sides. Mitigations: broadcast the small side to avoid the shuffle entirely, salt the hot key by appending a random suffix and joining on the salted key before re-aggregating, or split the hot keys into a separate job.
What is the difference between a clustered and non-clustered index?
A clustered index defines the physical order of rows on disk, so there can be exactly one per table and range scans on it are sequential. A non-clustered index is a separate structure holding the key plus a pointer back to the row, so there can be many, and a lookup that needs columns outside the index pays an extra fetch. Covering the query with an included column avoids that fetch.
What are the ACID properties?
Atomicity: a transaction fully commits or fully rolls back. Consistency: it moves the database from one valid state to another, respecting constraints. Isolation: concurrent transactions do not observe each other's intermediate state, tunable through isolation levels. Durability: once committed, the write survives a crash. The follow-up is usually about isolation levels and which anomalies each one permits.
What is the difference between DELETE, TRUNCATE, and DROP?
DELETE removes rows one at a time, is transactional, fires triggers, and can carry a WHERE clause. TRUNCATE deallocates whole pages, so it is far faster, resets identity sequences, and cannot be filtered. DROP removes the table definition itself. The practical interview point is that TRUNCATE is minimally logged and hard to undo, so it belongs in rebuild jobs, not in production cleanup with a predicate.
What is normalization, and when do you deliberately denormalize?
Normalization removes redundancy across forms: 1NF requires atomic values, 2NF removes partial dependencies on a composite key, 3NF removes transitive dependencies. It protects write integrity. Analytical warehouses then denormalize deliberately, collapsing dimensions into wide tables so reads join less. Both are correct in their own layer, and naming which layer you are designing for is the answer interviewers want.
What does a CROSS JOIN do and when is it actually useful?
It produces the Cartesian product, every row on the left paired with every row on the right. Usually it is a bug from a missing join predicate. Legitimately, it generates dense grids: crossing a date spine with a dimension list so every combination exists before a LEFT JOIN fills in the sparse measures, which is how you report zeros for days that had no activity.
What is the difference between a primary key and a unique key?
Both enforce uniqueness. A primary key is one per table, cannot be NULL, and is the row's identity. A unique constraint can be declared many times per table and typically permits one NULL, since NULL is not equal to itself under three-valued logic. In a warehouse the primary key is usually a surrogate key issued by the warehouse, with the source system's natural key kept as a unique constraint.
What are the SQL execution order rules?
Written order is not evaluation order. Evaluation runs FROM and JOIN, then WHERE, GROUP BY, HAVING, window functions, SELECT, DISTINCT, ORDER BY, and finally LIMIT. This explains the two rules candidates trip on: a SELECT alias is not visible to WHERE because SELECT has not run yet, and a window function cannot appear in the same query's WHERE because windows are computed after filtering.
How do you pivot rows into columns in SQL?
Conditional aggregation: SUM(CASE WHEN category = 'x' THEN amount END) as one column per category, grouped by the row key. Some dialects offer a PIVOT operator, but the CASE form is portable and is what interviewers expect. The trap is omitting the ELSE and then using AVG, which drops non-matching rows from the denominator instead of counting them as zero.
What is a materialized view and how does it differ from a view?
A view is a stored query, re-executed on every read, so it is always current and costs full computation each time. A materialized view stores the computed result, so reads are cheap but the data is as stale as the last refresh. The engineering question is refresh strategy: full rebuild versus incremental, and what staleness the consumer tolerates.