DE Query Optimization

A query that returns the correct result in 45 minutes is not good enough in a production pipeline that must complete in 5 minutes. Query optimization is the discipline of making SQL queries run faster and use fewer resources without changing their output. Data engineers who understand optimization keep pipelines on schedule and prevent costly compute overruns in cloud data warehouses.

How a Database Executes a Query

Before optimizing, understand what happens when a database receives a SQL query. The database parses the SQL text, generates multiple possible execution plans (ways to retrieve the data), estimates the cost of each plan, and chooses the plan it believes will be fastest. This process runs through the query optimizer — an internal engine that makes most optimization decisions automatically. But the optimizer can only work with what the engineer provides: good table statistics, proper indexes, and well-written queries.

The Iceberg Analogy

A slow query is like an iceberg. What you see above the surface is a few lines of SQL. What causes the slowness hides below: missing indexes, full table scans, unnecessary joins, exploding row counts from cross joins, poor partitioning. Optimization means diving below the surface and understanding the structure of the execution plan.

Using EXPLAIN to Understand Query Plans

Most databases support an EXPLAIN command that shows the query execution plan without actually running the query. Data engineers read EXPLAIN output to identify bottlenecks.

EXPLAIN SELECT customer_id, SUM(amount)
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id;

EXPLAIN output (simplified):
HashAggregate  (cost=12450.00)
  -> Seq Scan on orders  (cost=0.00..10500.00)
        Filter: (order_date >= '2024-01-01')

"Seq Scan" means the database reads every row in the table.
This is expensive on large tables and signals a missing index.

Indexes: The Fastest Optimization

An index on a frequently filtered or joined column allows the database to jump directly to matching rows rather than scanning the entire table. Adding the right index often reduces query time from minutes to milliseconds.

-- Without index: Seq Scan reads all 10 million rows
SELECT * FROM orders WHERE order_date = '2024-05-15';

-- Add index on order_date
CREATE INDEX idx_orders_date ON orders(order_date);

-- After index: Index Scan reads only matching rows (perhaps 5,000)
SELECT * FROM orders WHERE order_date = '2024-05-15';

Composite Indexes

When queries filter on multiple columns together, a composite index covering those columns together outperforms separate single-column indexes.

-- Query filters on both customer_id and order_date together
SELECT * FROM orders
WHERE customer_id = 'C001' AND order_date >= '2024-01-01';

-- Composite index covers both filter columns
CREATE INDEX idx_orders_cust_date ON orders(customer_id, order_date);

Reduce Data Early with Filters

The earlier a query filters out rows, the fewer rows subsequent operations must process. Filtering inside a subquery or CTE before joining to other tables reduces the join workload dramatically.

-- BAD: Join first, then filter (joins all 10M rows)
SELECT c.name, o.amount
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2024-01-01';

-- BETTER: Filter orders first, then join (joins only 50K filtered rows)
WITH recent_orders AS (
    SELECT customer_id, amount
    FROM orders
    WHERE order_date >= '2024-01-01'
)
SELECT c.name, ro.amount
FROM customers c
JOIN recent_orders ro ON c.customer_id = ro.customer_id;

Avoid SELECT * in Production

SELECT * reads every column from the table, including those the query does not need. On wide tables in a columnar warehouse, this wastes significant I/O. Selecting only needed columns reduces the data the engine must read and transmit.

-- BAD: reads all 80 columns
SELECT * FROM fact_sales WHERE sale_date = '2024-05-15';

-- BETTER: reads only 3 needed columns
SELECT sale_date, product_id, revenue
FROM fact_sales WHERE sale_date = '2024-05-15';

Partitioning: Physical Data Organization

Large tables in data warehouses partition into smaller physical segments based on a column value — usually a date. A query that filters by date skips all partitions outside the date range, reading only the relevant data.

-- Table partitioned by month
-- Query scans ONLY the May 2024 partition (1/12th of the data)
SELECT product_id, SUM(revenue)
FROM fact_sales
WHERE sale_month = '2024-05'
GROUP BY product_id;

Without partitioning: scans all 5 years of history
With partitioning by month: scans 1 month only

Avoid Functions on Indexed Columns in WHERE

Wrapping a filtered column in a function prevents the database from using an index on that column. The function must evaluate for every row, forcing a full table scan.

-- BAD: YEAR() function prevents index use on order_date
SELECT * FROM orders WHERE YEAR(order_date) = 2024;

-- GOOD: Range filter uses the index on order_date
SELECT * FROM orders
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';

Join Order and Selectivity

Join the most selective table first — the one that filters out the most rows. Joining a million-row fact table to a small 50-row dimension table is fast. Joining two million-row tables and then filtering is slow. Most modern query optimizers handle this automatically, but explicit filtering before large joins always helps.

Caching and Materialized Views

When the same expensive query runs repeatedly, materializing its results prevents recalculation. A materialized view precomputes and stores the result of a complex query. Subsequent reads hit the stored result instead of rerunning the full computation.

-- Create a materialized view for a heavy aggregation
CREATE MATERIALIZED VIEW monthly_revenue_summary AS
SELECT
    DATE_TRUNC('month', sale_date) AS month,
    region,
    SUM(revenue) AS total_revenue
FROM fact_sales
GROUP BY 1, 2;

-- Refresh when underlying data changes
REFRESH MATERIALIZED VIEW monthly_revenue_summary;

Summary

Query optimization improves execution speed and reduces compute cost without changing query results. Key techniques include adding indexes on filtered and joined columns, pushing filters early to reduce row counts before joins, selecting only needed columns, using table partitioning to skip irrelevant data, and materializing expensive repeated queries. Understanding the query execution plan through EXPLAIN gives data engineers the visibility needed to target optimizations precisely.

Leave a Comment

Your email address will not be published. Required fields are marked *