Last week we hosted a hands-on SQL workshop with a master of the craft, Ergest Xheblati. He’s spent the last 15 years refining his SQL skills and captured those skills in his book, Minimum Viable SQL Patterns. In this workshop, Ergest explains and demonstrates various principles from his book, such as:
🎯 Query decomposition patterns - Solve complex queries by systematically decomposing them into smaller ones
🎯 Query maintainability patterns - DRY principle (don't repeat yourself)
🎯 Query performance patterns - Make your queries faster (and cheaper)
Toward the end of the workshop, Ergest answered over a dozen questions from SQL professionals all over the world. Here’s a summary of all the major topics covered. 👇
As SQL practitioners, we often find ourselves writing 50+ line queries to answer business questions. Sure, these queries get the answers we’re looking for, but without discipline, they can be annoyingly difficult to read. And if you, the creator, can barely follow along with the query, it’s unlikely that your data team will be able to either.
The solution? Common Table Expressions (CTEs).
When you use CTEs correctly, you can break down a large query into smaller, independent pieces (AKA decompose them), allowing you to easily read your query as a direct acyclic graph (DAG). Ergest used a real-life example in his video with several subqueries converted to CTEs, but here’s a simple side-by-side comparison from Alisa Aylward.
WITH avg_pet_count_over_time AS ( SELECT cat_id, MAX(timestamp)::DATE AS max_pet_date, MIN(timestamp)::DATE AS min_pet_date FROM cat_pet_fact GROUP BY 1 ) SELECT cat_name, t1.max_pet_date, t2.min_pet_date FROM cat_dim LEFT JOIN avg_pet_count_over_time as t1 ON cat_dim.cat_id = t1.cat_id LEFT JOIN avg_pet_count_over_time as t2 ON cat_dim.cat_id = t2.cat_id;
SELECT cat_name, t1.max_pet_date, t2.min_pet_date FROM cat_dim LEFT JOIN (SELECT cat_id, MAX(timestamp)::DATE AS max_pet_date, MIN(timestamp)::DATE AS min_pet_date FROM cat_pet_fact GROUP BY 1) AS t1 ON cat_dim.cat_id = t1.cat_id LEFT JOIN (SELECT cat_id, MAX(timestamp)::DATE AS max_pet_date, MIN(timestamp)::DATE AS min_pet_date FROM cat_pet_fact GROUP BY 1) as t2 ON cat_dim.cat_id = t2.cat_id;
Notice how much easier the query with CTEs is to read?
In fact, switching from nested subqueries to CTEs is similar to switching from a messy bedroom to an organized one, Ergest highlighted in a recent tweet. Like a clean room, a CTE query is more manageable – plus, it’s much easier to find whatever you’re looking for if it’s always in the right place.
After learning how to make queries more readable, Ergest explained how CTEs make queries more maintainable. Whenever you need to debug a query, you can investigate the individual DAG-like CTEs from beginning to end until you find the issue. This can save you hours in a day and allow you to get started on your never-ending to-do list. 📝
Ergest also discussed the don’t-repeat-yourself (DRY) principle. Here’s the TL;DR: If you find yourself copying and pasting code frequently, you’re better off creating views for your CTEs, which reduces the lines of code per query.
Query performance patterns
Next, Ergest described how to make queries more performant and cost-effective, something he calls a “query performance pattern.” He shared a few rules to follow 👇
SELECTstatement. Sort operations (ex.
ORDER BY) aren’t necessary until your query is in its final format.
WHEREClauses are capable of handling complex functions, but they reduce performance. It’s negligible when dealing with small queries, but if you’re dealing with millions or rows, it can get costly, so make the where clauses as simple as possible.
Following these rules will reduce query run times and save your organization money. 💰 Even if you’re dealing with small amounts of data now, practicing query performance patterns will make you a SQL expert in the long term.
After chatting about the three principles above, Ergest answered dozens of questions from SQL practitioners around the world. These are three (of the many) that I found valuable:
🤔 Why should you wait until the end of a query to join data? Is it for performance or organization?
🤔 What are your suggestions for where and how to comment in SQL?
🤔 What is the performance/readability difference between using CTE and temp tables?
This blog is a brief summary of everything Ergest taught in the workshop. If you’d like to level up your SQL skills and learn more, check out the full workshop here.
✨ Then head on over to join the Operational Analytics Club so you can discuss what you learned!