Skills

sql-query-explainer

Data & analytics v3 Emdly recommended
@querydeck 166 installs updated 23d ago MIT license

SQL query explainer

Reads a query the way a reviewer should: what rows come out, which rows silently disappear, and where the time goes.

When to use

  • On any query in a PR, a dashboard, or a ticket ("why is this slow", "why is this number wrong").
  • With EXPLAIN (ANALYZE) output when available — the explanation gets sharper.

Input

The query, the dialect (PostgreSQL, MySQL, SQLite, BigQuery…), table definitions if available, and optionally the plan.

Explanation, in this order

  1. One sentence: what the result set is, in business terms ("one row per customer with their last paid invoice").
  2. Grain. What one output row represents. If a join can multiply rows (one-to-many without aggregation), say so — this is the most common wrong-number bug.
  3. Filters that drop rows silently: INNER JOIN on optional relations, WHERE col = x on a nullable column (NULLs vanish), NOT IN with NULLs (returns nothing), DISTINCT hiding a join fan-out.
  4. NULL semantics wherever they change the answer.
  5. Where the time goes. From the plan if given; otherwise from the shape: sequential scans on filtered columns, functions on indexed columns (WHERE date(created_at) = …), ORDER BY without a supporting index, OR across columns.
  6. The index it wishes existed — as a concrete statement with the predicate it serves. Say when an index would not help (low selectivity, tiny table).

Rules

  • Never rewrite the query unless asked; explain it. If a rewrite is the fix, show the smallest one and state that the result set is identical (or how it differs).
  • Dialect matters: say when a claim is dialect-specific.
  • If tables are unknown, say which assumption the explanation rests on.

Output format

**Result:** one row per customer who has at least one invoice, with the total of *paid* invoices.

**Grain:** customer. Safe — `SUM` aggregates the invoices join.

**Silent drops:** `INNER JOIN invoices` removes customers with no invoices; `WHERE status = 'paid'` moves into the join filter — customers with only unpaid invoices also vanish. If you want them at 0, use `LEFT JOIN … AND status = 'paid'`.

**Time:** `WHERE date(created_at) = current_date` prevents the index on `created_at`. Use a range: `created_at >= current_date AND created_at < current_date + 1`.

**Index:** `CREATE INDEX invoices_customer_status ON invoices (customer_id, status) WHERE status = 'paid';` — serves the join + filter. (PostgreSQL partial index.)

License

MIT