# Query Plan Visualizer > PostgreSQL EXPLAIN (ANALYZE) or MySQL EXPLAIN JSON → plan tree, hot nodes and index suggestions. Paste PostgreSQL EXPLAIN / EXPLAIN ANALYZE output (text or FORMAT JSON) or MySQL EXPLAIN FORMAT=JSON. The plan is drawn as a node tree with cost, estimated vs actual rows, inclusive and exclusive time per node, and checked for the classic problems: row misestimates, sequential scans that discard most rows, sorts and hashes spilling to disk, nested loops over large inputs — each with a concrete fix such as a specific CREATE INDEX statement or work_mem setting. Part of usefulweb.site (https://usefulweb.site/). Results are deterministic calculations from the inputs and the listed assumptions; the API returns exactly what the web page shows. ## Use it - Human interface: https://queryplanvisualizer.usefulweb.site/ (interactive tool at https://queryplanvisualizer.usefulweb.site/tool) - JSON API: POST https://queryplanvisualizer.usefulweb.site/api/v1/calculate with a JSON object of inputs (schema: https://queryplanvisualizer.usefulweb.site/api/v1/tool) - OpenAPI: https://queryplanvisualizer.usefulweb.site/openapi.json - MCP (Streamable HTTP): https://queryplanvisualizer.usefulweb.site/mcp - tool `query_plan_visualizer` ## Inputs - text (textarea): EXPLAIN output - PostgreSQL: EXPLAIN (ANALYZE, BUFFERS) … as text, or EXPLAIN (ANALYZE, FORMAT JSON). MySQL: EXPLAIN FORMAT=JSON …. psql headers, "+" line continuations and "(N rows)" footers are ignored. - file (file): Or upload the file - Read in memory as UTF-8 text and not stored. Max 5 MiB. - misestimateFactor (number): Flag row misestimates from - Ratio between estimated and actual rows per loop. ## Method - PostgreSQL text plans are parsed by the "->" indentation; node headers give cost=startup..total rows width and (actual time=startup..total rows loops); detail lines (Filter, Rows Removed by Filter, Sort Method, Buckets/Batches, Buffers…) are mapped to the same keys as FORMAT JSON. - Actual time and rows are per-loop averages (PostgreSQL docs), so inclusive time = actual total time × loops; exclusive time = inclusive − children's inclusive time. Under Gather, time is divided by the number of participating processes. - Misestimate = max(estimated, actual)/min(…) rows per loop at or above the threshold (and ≥100 rows absolute difference). - Rules: Seq Scan with Filter removing ≥90% of ≥1000 rows; Index Scan still filtering; Sort Method external/Disk; Hash Batches > 1; Nested Loop with ≥1000 outer rows and a sequential inner side or ≥10k inner loops; lossy bitmaps; workers not launched; heap fetches in index-only scans. - Index candidates take equality-compared filter columns first, then the first range column (B-tree column-order rule); functions in filters call for expression indexes and leading-wildcard LIKE for pg_trgm. - MySQL: access_type ALL / index, filtered %, attached_condition columns, using_join_buffer, using_filesort and using_temporary_table (EXPLAIN FORMAT=JSON has estimates only, no timings).