What this does
Paste the output of EXPLAIN or EXPLAIN ANALYZE (PostgreSQL's standard text format) and this reconstructs the plan tree, then computes each step's own cost or time — excluding whatever its child steps already accounted for. That distinction matters: the top node in a plan always shows the largest cumulative cost, since it includes everything beneath it, which makes it easy to misread the outer node as "the slow part" when the actual bottleneck is buried three levels down. Ranking by self time instead of cumulative time points at the step that's actually expensive.
Scope and limitations
This parses PostgreSQL's standard indented text output specifically — not MySQL's tabular EXPLAIN, not any database's JSON or XML plan format, and not SQL Server's graphical plans. When the input doesn't match that format, the tree analysis is skipped, but plan-related terms found anywhere in the text (MySQL's Extra column values included) are still matched against the glossary below, so pasting a different format still gets you some value.
It also isn't a substitute for actually understanding your query or your data — the flags it raises (a large sequential scan, a big gap between estimated and actual row counts, a filter discarding more rows than it kept) are heuristics worth a second look, not guaranteed problems. A sequential scan on a small table is often exactly the right plan.
Term glossary
Seq Scan— Reads every row in the table, in physical order. Fine for small tables; a red flag for large ones if only a few rows are actually needed.Index Scan— Uses an index to find matching rows, then fetches each row's full data from the table.Index Only Scan— Answers the query from the index alone, without touching the table at all — the fastest scan type when every needed column is in the index.Bitmap Heap Scan— Fetches rows from the table using a bitmap of locations built by a Bitmap Index Scan, in one physically-ordered pass. Common when a query matches many, but not all, rows.Bitmap Index Scan— Builds a bitmap of matching row locations from an index, feeding a Bitmap Heap Scan above it.Nested Loop— For each row from the outer input, scans or looks up matches in the inner input. Efficient when the outer side is small.Hash Join— Builds a hash table from one input, then probes it with the other. Typically fast for larger equi-joins.Merge Join— Merges two already-sorted inputs. Efficient when both sides are sorted, e.g. by an index.Sort— Sorts rows — used for ORDER BY, merge joins, or grouped aggregates. Can spill to disk if it doesn't fit in memory.HashAggregate— Computes GROUP BY / aggregate functions by hashing groups, without requiring sorted input.GroupAggregate— Computes GROUP BY / aggregate functions relying on already-sorted input.Limit— Stops producing rows once the requested number (LIMIT) has been returned.Materialize— Caches a subplan's output so it can be re-scanned cheaply — common inside nested loops.CTE Scan— Reads from a previously computed WITH clause result.Subquery Scan— Reads from a subquery's result set.Append— Combines rows from multiple child plans — e.g. UNION ALL, or scanning multiple partitions.WindowAgg— Computes window functions (OVER (...)).Gather Merge— Collects sorted rows from parallel worker processes, preserving order.Gather— Collects rows from parallel worker processes.Filter— A condition applied after rows are read, discarding non-matching ones — unlike an index condition, a Filter doesn't reduce how much was read in the first place.Index Cond— The condition used to narrow an index scan itself — ideally where most of the filtering happens, since it avoids reading rows that don't match.Rows Removed by Filter— How many rows a Filter discarded after they were already read — a high number here often means an index on the filtered column would help.Using filesort— (MySQL) An extra sorting pass was needed instead of reading rows in index order — slower than using an index that already matches the ORDER BY.Using temporary— (MySQL) A temporary table was needed, usually for a GROUP BY/DISTINCT/ORDER BY combination that couldn't be resolved directly — often another sign a helpful index is missing.Using index condition— (MySQL) Index Condition Pushdown — part of the WHERE clause was evaluated using the index before the full row was fetched.Using join buffer— (MySQL) No usable index existed for this join, so rows were buffered in memory to avoid rescanning — usually means a missing index on the join column.Using where— (MySQL) A WHERE filter was applied after the storage engine returned rows, rather than the index narrowing what was read.Using index— (MySQL) The query was satisfied from the index alone, without reading the table — equivalent to PostgreSQL's Index Only Scan.
How this is computed
Parsing and analysis happen entirely in your browser via a small hand-written parser — a query plan can reveal table names, column names, and row counts you might not want to paste into a random web form, so nothing here is ever sent anywhere.
Try it yourself
If you're staring at the query itself rather than its plan, How to Read Complex SQL Queries covers the reading side. Reformat a messy query first with SQL Formatter before you go looking for why it's slow.