Cohort 4Register
Data Engineering Skills

SQL for Data Engineers: The Queries You Write at Work, and How Interviews Test Each One

A data engineer's SQL builds tables other people rely on, so it has to keep the grain, survive a rerun, keep history and run fast. Here is each pattern with a short example and the interview question that tests it.

By Durgesh Yadav — Senior Data Engineer @ 7-Eleven · Updated 15 Oct 2026. Preparation guidance, not a hiring guarantee.

What SQL does a data engineer need to know?

Joins that keep the grain, window functions, CTEs, MERGE for upserts, slowly changing dimensions, partition pruning, query plans and data quality checks. A data engineer's SQL is judged on whether it is correct, safe to rerun and fast at scale. SQL is named in 48.6% of 1,470 data-engineer postings in PrepNPlaced's India Tech Hiring Report, more than any other skill.

Guide

What To Learn And How To Practice

Which SQL does a data engineer write at work?

A data engineer writes SQL that builds tables, where an analyst's mostly reads them, so mistakes cost more: a join that duplicates rows overstates every number built on it, and a load that is not safe to rerun doubles a table on retry. SQL leads the India Tech Hiring Report's data-engineer skills at 48.6% of 1,470 postings.

How interviews test the SQL a data engineer writes
PatternWhat it does in a pipelineHow interviews test it
Joins at scaleCombines tables without duplicating or losing rowsRevenue rose after you added a join. Why?
Window functionsPicks the latest record, ranks, compares a row with the previous oneKeep the latest row per customer
CTEsSplits a long transformation into named, testable stepsRewrite this nested query as CTEs
MERGE (upsert)Updates changed rows and inserts new ones in one statementWrite a load that is safe to run twice
Slowly changing dimensionsKeeps the history of an attribute, such as a customer's cityDesign an SCD Type 2 load and say which rows it touches
Partition pruningReads only the partitions a query needsThis query scans the whole table every day. Why?
Query plansShows the scans, joins and shuffles the engine choseRead this plan and say what you would change
Data quality checksStops a bad load before anyone reads itWhich checks run before this table is published?

How do joins go wrong at scale, and how do you prevent it?

Most join bugs are grain bugs: joining one row per order to many rows per order repeats each order once per match, so every SUM is too high with no error. Aggregate the many side first and count rows before and after. Interviewers also probe NOT IN over a nullable column, which returns no rows once the list holds a NULL (use NOT EXISTS), and skew, where one key leaves one Spark task doing most of the work.

State the grain of every table you join: one row per what?
In Spark SQL, a BROADCAST hint sends a small table to every executor; Spark does not guarantee it will follow the hint

Revenue doubled after a join

WITH items AS (SELECT order_id, COUNT(*) AS item_count FROM order_items GROUP BY order_id) SELECT o.order_date, SUM(o.amount) AS revenue, SUM(i.item_count) AS items FROM orders o LEFT JOIN items i ON i.order_id = o.order_id GROUP BY o.order_date. Joining orders straight to order_items repeats each amount once per item; aggregating items first keeps the grain.

How do window functions and CTEs show up in pipelines?

Window functions keep the latest version of each record, rank rows in a group, and compare a row with the previous one to detect a change; CTEs hold those steps in order so each can be tested alone. Know the deduplication pattern cold.

ROW_NUMBER gives one row per group; RANK and DENSE_RANK keep ties
LAG and LEAD compare a row with its neighbour
A window function cannot sit in WHERE: filter it in an outer query, or with QUALIFY on Snowflake

Keep the latest row per customer

WITH ranked AS (SELECT customer_id, city, updated_at, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC, ingested_at DESC) AS rn FROM raw_customers) SELECT customer_id, city, updated_at FROM ranked WHERE rn = 1. RANK would keep both rows when two share an updated_at; the second ORDER BY column makes the winner the same on every run.

How do you write a MERGE that is safe to rerun, and keep history with SCD Type 2?

A MERGE updates matching target rows and inserts the rest in one statement, so a retried job gives the same result. Databricks documents three clause types for MERGE INTO on Delta tables (WHEN MATCHED, WHEN NOT MATCHED and WHEN NOT MATCHED BY SOURCE), and the statement fails with DELTA_MULTIPLE_SOURCE_ROW_MATCHING_TARGET_ROW_IN_MERGE when two source rows match one target row, so deduplicate the source first.

SCD Type 1 overwrites; SCD Type 2 keeps every version with valid_from, valid_to and is_current
Compare nullable columns with IS DISTINCT FROM, not <>

An upsert that is safe to rerun

MERGE INTO dim_product AS t USING staged_products AS s ON t.product_id = s.product_id WHEN MATCHED AND s.updated_at > t.updated_at THEN UPDATE SET t.name = s.name, t.updated_at = s.updated_at WHEN NOT MATCHED THEN INSERT (product_id, name, updated_at) VALUES (s.product_id, s.name, s.updated_at). Run it twice with the same batch and the second run changes nothing.

SCD Type 2 in two statements

Close changed rows: MERGE INTO dim_customer AS t USING changes AS s ON t.customer_id = s.customer_id AND t.is_current = true WHEN MATCHED AND t.city IS DISTINCT FROM s.city THEN UPDATE SET t.is_current = false, t.valid_to = s.updated_at. Insert new versions: INSERT INTO dim_customer (customer_id, city, valid_from, valid_to, is_current) SELECT s.customer_id, s.city, s.updated_at, NULL, true FROM changes AS s LEFT JOIN dim_customer AS t ON t.customer_id = s.customer_id AND t.is_current = true WHERE t.customer_id IS NULL. A rerun after a failure between the two is safe.

How do you make SQL fast with partition pruning and query plans?

A query is fast when it reads and shuffles less data. Google's BigQuery documentation describes pruning as scanning the partitions that match the filter and skipping the rest, which happens only when you filter the partition column itself: a table partitioned by order_date and filtered on created_at reads everything. In Spark SQL, EXPLAIN shows the physical plan; EXTENDED, CODEGEN, COST and FORMATTED show more.

Exchange in a Spark plan means a shuffle; PartitionFilters on a file scan shows whether pruning happened
Adaptive Query Execution, on by default since Apache Spark 3.2.0, can split skewed partitions in sort-merge joins

Read the plan before you tune

EXPLAIN FORMATTED SELECT c.city, SUM(o.amount) AS revenue FROM orders o JOIN dim_customer c ON c.customer_id = o.customer_id WHERE o.order_date >= :start_date GROUP BY c.city. Check the orders scan for PartitionFilters on order_date. If dim_customer is small and the join is a SortMergeJoin, broadcast it so orders is not shuffled.

Which data quality checks should a pipeline run in SQL?

Each check is a query with an expected result, and the pipeline stops the load when the result is wrong. Write them for failures that raise no error, and keep one story ready of a check that caught a bad load.

Uniqueness: one current row per business key
Completeness and freshness: no NULL keys, and a recent newest timestamp
Reconciliation: counts match the source for the same day
Referential integrity: every foreign key finds its dimension row

Duplicate current keys must return zero rows

SELECT customer_id, COUNT(*) AS n FROM dim_customer WHERE is_current = true GROUP BY customer_id HAVING COUNT(*) > 1. Any row returned means two current versions of one customer, so the job fails before the table is published.

FAQ

Common Questions

Which SQL book should a data engineer read?

Start with Chapter 3 of PrepNPlaced's free Data Engineering book, SQL to Interview Level, then Chapter 8 for idempotent loads and MERGE. Keep your warehouse's SQL reference beside it, because MERGE and date functions differ between engines.

Is SQL enough to get a data engineering job?

No, but screening starts there: the India Tech Hiring Report found SQL in 48.6% of 1,470 data-engineer postings and Python in 42.7%, with a cloud and Spark after them.

How is SQL for data engineering different from SQL for data analysis?

An analyst's query answers a question once; a data engineer's builds a table that runs every day for other people, so it is judged on grain, rerun safety, history and cost too.

Which SQL dialect should I practise for interviews?

Standard SQL with window functions first, then your target employer's warehouse dialect. Spark SQL is a useful second, because the same queries run inside PySpark jobs.

How do I practise SQL for data engineering?

On a timer, checking results, not only syntax. In PrepNPlaced's Coding Practice, PySpark's Run executes your code on a real Spark sandbox and shows the first 50 rows, and Submit grades it.

Next Step

Turn The Guide Into Practice

Use PrepNPlaced tools to turn this learning path into resume proof, targeted practice, and interview-ready explanations.

Practise in Coding Practice