SQL explanation examples

See How Easily AI Explains SQL and Helps You Understand It

Explore practical SQL explanation examples for common PostgreSQL query patterns. Each example includes the original SQL query, a clause-by-clause breakdown, and an interpretation of the results.

  • Read SQL queries with line-by-line breakdowns
  • Understand joins, aggregates, CTEs, and window functions
  • Review expected output shapes and column data types
  • Use examples to document SQL intent for teams and reviews
  • Use with any database engine

What these SQL explainer examples cover

This page focuses on readable SQL explanations for real PostgreSQL query shapes, including filters, joins, CTEs, window functions, time grouping, and JSON expansion. Use these examples to understand query intent and communicate behavior clearly.

Each sample is rendered in structured markdown with original SQL, detailed explanations, summary notes, and expected output schema so teams can reuse explanations in docs and reviews.

SQL explanation examples

Understand how SQL queries work with line-by-line explanations and result expectations.

Example 1

Original SQL query

SELECT
    product_id,
    name,
    price,
    stock_quantity
FROM
    products
WHERE
    category = 'Electronics'
    AND price > 500.00
    AND stock_quantity > 0
ORDER BY
    price DESC;

Explainer output

πŸ› οΈ Breakdown

-- 1. Project four columns to return basic product details: identifier, display name, price and available quantity:
SELECT
    product_id,
    name,
    price,
    stock_quantity
-- 2. Source data comes from the products table:
FROM
    products
-- 3. Filter to only include items in the 'Electronics' category that cost more than 500.00 and have stock_quantity > 0 β€” i.e., high‑value electronics that are currently available for sale:
WHERE
    category = 'Electronics'
    AND price > 500.00
    AND stock_quantity > 0
-- 4. Sort results by price in descending order so the most expensive matching products appear first:
ORDER BY
    price DESC;

πŸ’¬ Explanation

#Explanation
1The SELECT clause lists four columns to return: product_id (the product identifier), name (the display name), price (the product price), and stock_quantity (how many units are available). These are the fields the query will project for each matching row.
2FROM products specifies the source table named products β€” the query reads rows from that table.
3The WHERE clause applies three filters combined with AND: category = 'Electronics' restricts to items in the Electronics category; price > 500.00 keeps only items with price greater than 500.00; stock_quantity > 0 ensures only products that are currently in stock. Together this yields high‑value Electronics that are available for sale.
4ORDER BY price DESC sorts the returned rows by the price column in descending order, so the most expensive matching products appear first.

🎯 Summary

Return identifier, name, price and available quantity for products in the products table that are Electronics, cost more than 500.00, and have stock on hand; present results with the most expensive items first.

πŸ“Š Returned result

product_idnamepricestock_quantity
1014K OLED TV 65"2499.995

❗ Returned types

product_idnamepricestock_quantity
integertextnumericinteger
Example 2

Original SQL query

SELECT
    u.username,
    COUNT(o.order_id) AS total_orders
FROM
    users u
JOIN
    orders o ON u.user_id = o.user_id
GROUP BY
    u.user_id, u.username
HAVING
    COUNT(o.order_id) > 3
ORDER BY
    total_orders DESC;

Explainer output

πŸ› οΈ Breakdown

-- 1. Select the user's username and the aggregated count of their orders, aliased as total_orders:
SELECT
    u.username,
    COUNT(o.order_id) AS total_orders
-- 2. From the users table joined to orders using user_id; this is an inner join so only users with matching orders are included:
FROM
    users u
JOIN
    orders o ON u.user_id = o.user_id
-- 3. Group results by user (user_id and username) so the COUNT is per-user; HAVING filters to only include users with more than 3 orders:
GROUP BY
    u.user_id, u.username
HAVING
    COUNT(o.order_id) > 3
-- 4. Order the final result set by total_orders in descending order to list the most-active buyers first:
ORDER BY
    total_orders DESC;

πŸ’¬ Explanation

#Explanation
1SELECT u.username, COUNT(o.order_id) AS total_orders: returns each user's username and the aggregated number of orders they have. COUNT(o.order_id) tallies non-NULL order_id values and is aliased as total_orders for readability.
2FROM users u JOIN orders o ON u.user_id = o.user_id: performs an inner join between users and orders on the user_id key. Because it is an inner join, only users who have at least one matching order in orders will appear in the results.
3GROUP BY u.user_id, u.username HAVING COUNT(o.order_id) > 3: GROUP BY collapses rows so the COUNT is computed per user. Both user_id and username are listed to satisfy grouping rules and uniquely identify each user. HAVING filters those groups after aggregation, keeping only users with more than 3 orders.
4ORDER BY total_orders DESC: sorts the final result so users with the largest total_orders appear first (most-active buyers listed at the top).

🎯 Summary

List usernames and their order counts for users who have placed more than three orders, showing only users with orders and sorted by highest order count first.

πŸ“Š Returned result

column 1column 2
alice12
bob7
carol4

❗ Returned types

column 1column 2
textbigint
Example 3

Original SQL query

WITH ProductRevenue AS (
    SELECT
        product_id,
        SUM(quantity * price_at_purchase) AS total_revenue
    FROM
        order_items
    GROUP BY
        product_id
)
SELECT
    p.name,
    pr.total_revenue
FROM
    products p
JOIN
    ProductRevenue pr ON p.product_id = pr.product_id
ORDER BY
    pr.total_revenue DESC
LIMIT 5;

Explainer output

πŸ› οΈ Breakdown

-- 1. Defines a CTE named ProductRevenue that calculates total_revenue per product by summing quantity * price_at_purchase from order_items:
WITH ProductRevenue AS (
    SELECT
        product_id,
        SUM(quantity * price_at_purchase) AS total_revenue
    FROM
        order_items
    GROUP BY
        product_id
)
-- 2. Main SELECT pulls the product name and the computed total_revenue from the products table joined to the CTE:
SELECT
    p.name,
    pr.total_revenue
FROM
    products p
-- 3. The JOIN is an inner join: only products that appear in order_items (have sales) will be included:
JOIN
    ProductRevenue pr ON p.product_id = pr.product_id
-- 4. Results are ordered by total_revenue descending to rank products by revenue:
ORDER BY
    pr.total_revenue DESC
-- 5. LIMIT 5 returns only the top five revenue-generating products; note that ties in revenue have no deterministic secondary ordering:
LIMIT 5;

πŸ’¬ Explanation

#Explanation
1The CTE ProductRevenue computes total_revenue per product by aggregating order_items: it multiplies quantity * price_at_purchase for each order item and SUMs those values, grouping by product_id. Only product_ids present in order_items appear in this result.
2The main SELECT returns the product name (p.name) and the precomputed total_revenue (pr.total_revenue) by reading products (alias p) and the CTE (alias pr).
3The JOIN is an inner join (JOIN ... ON p.product_id = pr.product_id), so only products that have matching rows in the ProductRevenue CTE (i.e., have at least one sale in order_items) are included.
4ORDER BY pr.total_revenue DESC sorts the output from highest revenue to lowest, ranking products by their revenue.
5LIMIT 5 restricts the output to the top five rows. If multiple products share the same total_revenue, their relative order is not deterministic unless you add a secondary ORDER BY (e.g., product_id or name).

🎯 Summary

Return the top 5 products by total sales revenue, showing each product's name and aggregated revenue computed from order_items.

πŸ“Š Returned result

nametotal_revenue
SuperWidget12500.75
MegaGadget9875.00
BasicWidget5400.00
ProThing4320.50
MiniGizmo3100.00

❗ Returned types

nametotal_revenue
textnumeric
Example 4

Original SQL query

WITH RankedProducts AS (
    SELECT
        name,
        category,
        price,
        RANK() OVER (PARTITION BY category ORDER BY price DESC) as price_rank
    FROM
        products
)
SELECT
    name,
    category,
    price
FROM
    RankedProducts
WHERE
    price_rank <= 2;

Explainer output

πŸ› οΈ Breakdown

-- 1. Defines a CTE named RankedProducts that will hold product rows augmented with a computed price_rank:
WITH RankedProducts AS (
-- 2. Selects name, category, price and computes price_rank using RANK() OVER (PARTITION BY category ORDER BY price DESC) β€” ranking products within each category by descending price (highest price gets rank 1):
    SELECT
        name,
        category,
        price,
        RANK() OVER (PARTITION BY category ORDER BY price DESC) as price_rank
-- 3. Reads rows from the products table to feed the window function; the CTE body ends here:
    FROM
        products
)
-- 4. Main query selects name, category, price from the CTE (RankedProducts):
SELECT
    name,
    category,
    price
FROM
    RankedProducts
-- 5. Filters to keep only the top two ranks per category (price_rank <= 2). Note: because RANK() is used, ties at a rank will include all tied rows and can return more than two products for a category:
WHERE
    price_rank <= 2;

πŸ’¬ Explanation

#Explanation
1The WITH clause defines a CTE (common table expression) named RankedProducts β€” a temporary, named result set that the main query can read from. It will contain product rows plus a computed price_rank column.
2Inside the CTE we SELECT name, category, price and compute price_rank with RANK() OVER (PARTITION BY category ORDER BY price DESC). This ranks products within each category by descending price so the highest-priced product(s) get rank 1. RANK() gives equal ranks to ties and can leave gaps in subsequent ranks.
3The FROM products clause supplies the rows the window function processes; the window (partitioning and ordering) is applied across those product rows. The CTE body ends after this SELECT.
4The main query reads from the CTE (RankedProducts) and selects the name, category, and price columns produced by the CTE.
5The WHERE price_rank <= 2 filter keeps only rows whose rank within their category is 1 or 2. Because RANK() preserves ties, categories with tied prices at rank 2 can return more than two products.

🎯 Summary

Returns products (name, category, price) that are ranked first or second by price within each category; ties at a rank are included.

πŸ“Š Returned result

namecategoryprice
Phone XElectronics999.99
Phone YElectronics899.99
Phone ZElectronics899.99
Novel ABooks19.99
Novel BBooks14.99
ToasterAppliances29.99
KettleAppliances29.99

❗ Returned types

namecategoryprice
TEXTTEXTNUMERIC
Example 5

Original SQL query

SELECT
    DATE_TRUNC('month', o.order_date)::date AS sales_month,
    SUM(oi.quantity * oi.price_at_purchase) AS monthly_revenue
FROM
    orders o
JOIN
    order_items oi ON o.order_id = oi.order_id
WHERE
    o.order_date >= '2023-01-01' AND o.order_date < '2024-01-01'
GROUP BY
    sales_month
ORDER BY
    sales_month;

Explainer output

πŸ› οΈ Breakdown

SELECT
    -- 1. DATE_TRUNC('month', o.order_date)::date creates the month bucket (first day of the month) used as sales_month; SUM(oi.quantity * oi.price_at_purchase) computes revenue using the recorded price at the time of purchase (protects against later price changes):
    DATE_TRUNC('month', o.order_date)::date AS sales_month,
    SUM(oi.quantity * oi.price_at_purchase) AS monthly_revenue
FROM
    -- 2. Inner JOIN between orders and order_items ensures only order items linked to an order are counted (orders without items are excluded). The join uses order_id to relate records:
    orders o
JOIN
    order_items oi ON o.order_id = oi.order_id
WHERE
    -- 3. WHERE restricts to the calendar year 2023 using a half-open interval (>= '2023-01-01' AND < '2024-01-01'), which is index-friendly and avoids inclusive/exclusive endpoint ambiguity:
    o.order_date >= '2023-01-01' AND o.order_date < '2024-01-01'
    -- 4. GROUP BY aggregates revenue per sales_month and ORDER BY returns months in ascending order. Note: in PostgreSQL the SELECT alias (sales_month) is not visible to GROUP BY β€” use GROUP BY DATE_TRUNC('month', o.order_date)::date or GROUP BY 1 to avoid an error:
GROUP BY
    sales_month
ORDER BY
    sales_month;

πŸ’¬ Explanation

#Explanation
1DATE_TRUNC('month', o.order_date)::date β€” truncates each order_date to the first day of its month (e.g. 2023-03-15 -> 2023-03-01) and casts to date, producing the sales_month bucket. SUM(oi.quantity * oi.price_at_purchase) β€” multiplies each item's purchased quantity by the price recorded at purchase and sums those values, so revenue reflects the price at time of sale (protects against later price changes).
2JOIN order_items oi ON o.order_id = oi.order_id β€” an inner join that keeps only rows where an order has matching order_items. Orders with no items are excluded. The relationship is made via the order_id key in both tables.
3WHERE o.order_date >= '2023-01-01' AND o.order_date < '2024-01-01' β€” a half-open interval that selects all timestamps from the start of 2023 up to but not including the start of 2024. This pattern is index-friendly and avoids ambiguity about end-of-day inclusivity.
4GROUP BY sales_month and ORDER BY sales_month β€” the query aggregates revenue per month and returns months ascending. Note: PostgreSQL does not allow referencing the SELECT alias (sales_month) directly in GROUP BY; use GROUP BY DATE_TRUNC('month', o.order_date)::date or GROUP BY 1 instead to avoid an error.

🎯 Summary

The query computes total revenue per calendar month for orders placed in 2023 by summing quantity * price_at_purchase for each order item, grouping results by the month (first day of each month) and returning months in ascending order.

πŸ“Š Returned result

sales_monthmonthly_revenue
2023-01-0112500.75
2023-02-019780.50
2023-03-0114320.00

❗ Returned types

sales_monthmonthly_revenue
datenumeric
Example 6

Original SQL query

SELECT
    p.name AS product_name,
    u.username,
    r.comment,
    tag_element.tag
FROM
    reviews r
JOIN
    products p ON r.product_id = p.product_id
JOIN
    users u ON r.user_id = u.user_id
CROSS JOIN LATERAL
    jsonb_array_elements_text(r.metadata -> 'tags') AS tag_element(tag)
WHERE
    r.rating = 5
    AND r.metadata ->> 'verified_purchase' = 'true';

Explainer output

πŸ› οΈ Breakdown

-- 1. Selected output columns: product name, reviewer username, the review text, and a single tag value (tag_element.tag):
SELECT
    p.name AS product_name,
    u.username,
    r.comment,
    tag_element.tag
-- 2. Base table is reviews (aliased r). It is joined to products and users to retrieve product name and reviewer username via the foreign keys r.product_id -> products.product_id and r.user_id -> users.user_id:
FROM
    reviews r
JOIN
    products p ON r.product_id = p.product_id
JOIN
    users u ON r.user_id = u.user_id
-- 3. CROSS JOIN LATERAL with jsonb_array_elements_text(r.metadata -> 'tags') expands the JSONB array stored at metadata->'tags' into one text row per tag. LATERAL lets the function reference the current review row (r), so each tag becomes its own output row:
CROSS JOIN LATERAL
    jsonb_array_elements_text(r.metadata -> 'tags') AS tag_element(tag)
-- 4. Filters to include only 5-star reviews that are marked as a verified purchase. Uses ->> to extract the JSON text value of metadata['verified_purchase'] and compares it to the string 'true':
WHERE
    r.rating = 5
    AND r.metadata ->> 'verified_purchase' = 'true';

πŸ’¬ Explanation

#Explanation
1SELECT picks four output columns: p.name AS product_name returns the product's name with the alias product_name; u.username returns the reviewer's username; r.comment returns the review text; tag_element.tag returns a single tag value produced by the lateral JSON expansion.
2FROM reviews r is the base table. Two inner JOINs pull related data: JOIN products p ON r.product_id = p.product_id uses the review's product_id to get the product row; JOIN users u ON r.user_id = u.user_id uses the review's user_id to get the reviewer row.
3CROSS JOIN LATERAL jsonb_array_elements_text(r.metadata -> 'tags') AS tag_element(tag) calls a set-returning function on the current review's metadata->'tags' JSONB array. jsonb_array_elements_text returns each array element as text; LATERAL allows the function to reference the current r row, and CROSS JOIN produces one output row per tag for that review. The alias tag_element(tag) names the resulting column tag.
4WHERE r.rating = 5 AND r.metadata ->> 'verified_purchase' = 'true' filters to only 5-star reviews that are marked as verified purchases. The ->> operator extracts the JSON value as text, so the comparison checks the text string 'true'.

🎯 Summary

Returns one row per tag for reviews that are 5 stars and marked verified, showing the product name, reviewer username, the review comment, and a single tag.

πŸ“Š Returned result

product_nameusernamecommenttag
Wireless Mouse XjdoeExcellent battery life and comfort.ergonomic

❗ Returned types

product_nameusernamecommenttag
texttexttexttext

SQL explanation checklist

Use this checklist when documenting SQL query behavior so explanations stay accurate, readable, and review-ready.

  • Confirm source tables, join keys, and filter scope before changing a query.
  • Validate grouping and window logic against the business question.
  • Document expected columns, result shape, and data types for reviewers.
  • Use consistent aliases and readable formatting to reduce SQL mistakes.

For a full workflow, pair this with the SQL validator, SQL formatter, and SQL optimizer.

SQL explainer FAQ

What does an SQL explainer provide?

An SQL explainer translates a query into plain language, including clause-level behavior, expected output, and common edge cases like join cardinality or grouping scope.

Can SQL explanations help during code review?

Yes. A clear explanation helps reviewers verify intent, catch logic mismatches, and confirm that filters, joins, and aggregates align with the product requirement.

Should I validate explained SQL before production use?

Yes. Pair explanations with syntax validation, formatting, and execution-plan checks so correctness, readability, and performance are all covered before release.