Approach: start from customers (to include those with zero orders) and LEFT JOIN only March 2024 orders and their order_items. Avoid joining before filtering because filtering in WHERE on a left-joined table can turn it into an inner join; also avoid joining a table that expands rows (e.g., items) before aggregating — that can double-count if you aggregate at the wrong grain. Best practice: either filter in the JOIN condition or aggregate detail rows first, then join.SQL (ANSI):sql
SELECT
c.customer_id,
c.name,
COALESCE(SUM(oi.amount), 0) AS revenue_march_2024
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
AND o.order_date BETWEEN DATE '2024-03-01' AND DATE '2024-03-31'
LEFT JOIN order_items oi
ON oi.order_id = o.order_id
GROUP BY c.customer_id, c.name
ORDER BY revenue_march_2024 DESC;
Key pitfalls and how to avoid them:- Filtering in WHERE on an outer-joined table (e.g., WHERE o.order_date BETWEEN ...) converts the LEFT JOIN to an INNER JOIN and drops customers with no March orders. Place the date filter in the ON clause or use WHERE o.order_date IS NULL OR ... to preserve NULLs.- Double-counting: if you join additional detail (order_items) and then join another detail-level table (e.g., shipments) without pre-aggregation, multiplicative row expansion can inflate sums. Fix by aggregating at a stable grain first (e.g., sum amounts per order or per order_item) and then joining those aggregates.- NULL amount values: use COALESCE(amount,0) when aggregating.- Returns/refunds: negative amounts must be modeled appropriately; include them in the sum if they exist.- Performance: for large data, pre-aggregate order_items (SUM by order_id) before joining to customers to reduce shuffle/scan size. Example pre-aggregation:sql
WITH order_sum AS (
SELECT order_id, SUM(COALESCE(amount,0)) AS order_amount
FROM order_items
GROUP BY order_id
)
SELECT c.customer_id, c.name, COALESCE(SUM(os.order_amount),0) AS revenue_march_2024
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
AND o.order_date BETWEEN DATE '2024-03-01' AND DATE '2024-03-31'
LEFT JOIN order_sum os
ON os.order_id = o.order_id
GROUP BY c.customer_id, c.name;
This preserves customers with zero orders and prevents aggregation errors from row explosion.