SQL Cheat Sheet for Data Analysts: Joins, CTEs, Window Functions, CASE, Dates and Ranking

Sep 8, 2026 | CompeteX

Data analysts rarely use SQL to answer isolated technical questions. Most tasks begin with a business question, such as which customers generated the most revenue, how monthly performance changed, which records are missing or which products rank highest within each category. Answering these questions often requires several SQL concepts to work together. 

For example, an analyst may need to join customer and order tables, filter completed transactions, group revenue by month and rank results within each region. Knowing the individual commands is useful, but knowing which pattern to apply, and in what order, is what turns raw data into a reliable answer. 

This cheat sheet brings the most reusable SQL patterns into one compact reference. It covers joins, common table expressions, conditional logic, date functions, window functions and ranking, using connected examples that show how the concepts support everyday analysis. 

Syntax note: Examples use PostgreSQL-style SQL. Check the date and row-limiting syntax if you use MySQL, SQL Server, BigQuery or another database. 

Sample schema: The examples use customers, orders and employees tables with common IDs, dates, amounts, status, region, department and salary fields. 

Logical order Clause Purpose 
FROM and JOIN Select and combine tables 
WHERE Filter rows 
GROUP BY Create groups 
HAVING Filter grouped results 
SELECT Choose output columns 
ORDER BY Sort results 
LIMIT Restrict returned rows 

SELECT region, SUM(amount) AS revenue 
FROM customers AS c 
JOIN orders AS o 
    ON c.customer_id = o.customer_id 
WHERE o.status = 'completed' 
GROUP BY region 
HAVING SUM(amount) > 1000 
ORDER BY revenue DESC

WHERE filters rows before aggregation. HAVING filters groups after aggregation. 

1. SQL Joins 
 

Join What it returns Typical use 
INNER JOIN Matches from both tables Customers who placed orders 
LEFT JOIN Every left-table row and available matches All customers, including those without orders 
FULL OUTER JOIN Matched and unmatched rows from both tables Comparing two datasets 
SELF JOIN Rows matched within the same table Employees and their managers 
CROSS JOIN Every possible row combination Creating combinations or data grids 

INNER JOIN 

SELECT 
    o.order_id, 
    c.customer_name, 
    o.amount 
FROM orders AS o 
INNER JOIN customers AS c 
    ON o.customer_id = c.customer_id; 

LEFT JOIN 

SELECT 
    c.customer_id, 
    c.customer_name, 
    COUNT(o.order_id) AS order_count 
FROM customers AS c 
LEFT JOIN orders AS o 
    ON c.customer_id = o.customer_id 
GROUP BY c.customer_id, c.customer_name; 

Use COUNT(o.order_id) instead of COUNT(*) when customers without orders should have an order count of zero. 

LEFT JOIN Filter Placement 

SELECT 
    c.customer_id, 
    COUNT(o.order_id) AS completed_orders 
FROM customers AS c 
LEFT JOIN orders AS o 
    ON c.customer_id = o.customer_id 
   AND o.status = 'completed' 
GROUP BY c.customer_id; 

Placing the order status condition in ON keeps customers without completed orders. Placing it in WHERE removes those customers from the result. 

2. Common Table Expressions 

A common table expression, or CTE, gives an intermediate result a name. It is useful when a query has several logical steps. 

WITH customer_revenue AS ( 
    SELECT 
        customer_id, 
        SUM(amount) AS total_revenue 
    FROM orders 
    WHERE status = 'completed' 
    GROUP BY customer_id 

SELECT 
    c.customer_name, 
    COALESCE(cr.total_revenue, 0) AS total_revenue 
FROM customers AS c 
LEFT JOIN customer_revenue AS cr 
    ON c.customer_id = cr.customer_id 
ORDER BY total_revenue DESC

Use a CTE when Use a subquery when 
The logic has multiple steps The logic is short and used once 
An intermediate result needs a clear name Nesting does not reduce readability 
The same result is referenced again No reuse is required 

A CTE is not automatically faster than a subquery. Performance depends on the database and its query plan. 

3. CASE WHEN 

CASE adds conditions to labels, calculations and aggregations. 

Create Categories 

SELECT 
    customer_id, 
    SUM(amount) AS total_revenue, 
    CASE 
        WHEN SUM(amount) >= 10000 THEN 'High value' 
        WHEN SUM(amount) >= 5000 THEN 'Medium value' 
        ELSE 'Low value' 
    END AS customer_segment 
FROM orders 
WHERE status = 'completed' 
GROUP BY customer_id; 

Conditional Aggregation 

SELECT 
    customer_id, 
    SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END
        AS completed_value, 
    SUM(CASE WHEN status = 'cancelled' THEN amount ELSE 0 END
        AS cancelled_value 
FROM orders 
GROUP BY customer_id; 

Requirement Pattern 
Create a label CASE WHEN amount >= 1000 THEN 'Large' ELSE 'Standard' END 
Count matching rows SUM(CASE WHEN condition THEN 1 ELSE 0 END) 
Sum matching values SUM(CASE WHEN condition THEN amount ELSE 0 END) 
Avoid dividing by zero value / NULLIF(denominator, 0) 

4. SQL Date Functions 

Date functions differ across database systems. 

Task PostgreSQL MySQL SQL Server 
Current date CURRENT_DATE CURDATE() CAST(GETDATE() AS date) 
Add seven days date + INTERVAL '7 days' DATE_ADD(date, INTERVAL 7 DAY) DATEADD(day, 7, date) 
Days between dates end_date - start_date DATEDIFF(end_date, start_date) DATEDIFF(day, start_date, end_date) 
Extract year EXTRACT(YEAR FROM date) YEAR(date) YEAR(date) 
Start of month DATE_TRUNC('month', date) DATE_FORMAT(date, '%Y-%m-01') DATEFROMPARTS(YEAR(date), MONTH(date), 1) 

Complete Date Range 

SELECT * 
FROM orders 
WHERE order_date >= DATE '2026-08-01' 
  AND order_date <  DATE '2026-09-01'; 

Using the first day of the next period as an exclusive end point includes every timestamp in August. 

5. Window Functions 

Window functions calculate across related rows without removing the original row-level detail. 

function_name(value) OVER ( 
    PARTITION BY group_column 
    ORDER BY sort_column 

Function Purpose 
SUM() OVER Running or grouped total 
LAG() Previous row's value 
ROW_NUMBER() Unique sequential number 
RANK() Rank with gaps after ties 
DENSE_RANK() Rank without gaps after ties 

Running Total 

SELECT 
    customer_id, 
    order_date, 
    amount, 
    SUM(amount) OVER ( 
        PARTITION BY customer_id 
        ORDER BY order_date, order_id 
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 
    ) AS running_total 
FROM orders; 

Previous Value 

SELECT 
    customer_id, 
    order_date, 
    amount, 
    LAG(amount) OVER ( 
        PARTITION BY customer_id 
        ORDER BY order_date, order_id 
    ) AS previous_amount 
FROM orders; 

6. ROW_NUMBER, RANK and DENSE_RANK 

Assume the ordered values are 90, 80, 80 and 70. 

Value ROW_NUMBER() RANK() DENSE_RANK() 
90 
80 
80 
70 

Top Three Employees per Department 

WITH ranked_employees AS ( 
    SELECT 
        employee_name, 
        department, 
        salary, 
        ROW_NUMBER() OVER ( 
            PARTITION BY department 
            ORDER BY salary DESC, employee_id 
        ) AS row_num 
    FROM employees 

SELECT * 
FROM ranked_employees 
WHERE row_num <= 3; 

Use ROW_NUMBER() to return exactly three rows per department. Use RANK() or DENSE_RANK() when tied values should share a position. 

Task Main SQL tools 
Find duplicate values GROUP BY, HAVING COUNT(*) > 1 
Find customers with no orders LEFT JOIN, IS NULL or NOT EXISTS 
Return the latest row per customer ROW_NUMBER() ordered descending 
Calculate month-over-month change Date grouping and LAG() 
Find the second-highest distinct value DENSE_RANK() 
Return the top N within each group CTE and ranking function 
Mistake Better approach 
Using column = NULL Use IS NULL 
Using WHERE for an aggregate filter Use HAVING 
Hiding duplicate joins with DISTINCT Check the join key and table relationship 
Ignoring ties in rankings Choose the ranking function deliberately 
Omitting a stable secondary sort Add a unique tie-breaker column 
Assuming every database uses the same dates Confirm the SQL dialect 

Reviewing query patterns is useful for understanding syntax, but practical ability develops when you apply those patterns to a dataset without being shown the complete solution. A single task might require you to decide which tables to join, how to handle missing records, which date range to use and whether ties should share the same rank. 

CompeteX data skills competitions provide structured challenges in SQL and data analytics where professionals can apply analytical concepts to practical problems. This helps move SQL learning beyond memorising commands and towards selecting the right approach, checking assumptions and producing a result that answers the underlying question. 

Technical References 

  1. PostgreSQL CTE documentation 
  1. PostgreSQL conditional expressions 
  1. PostgreSQL date and time functions 
  1. PostgreSQL window functions 
  1. MySQL window functions 
  1. SQL Server DATEDIFF 

Stay Updated with PangaeaX

Subscribe to our newsletter for the latest insights, updates, and
opportunities in data science.