DEV Community

Cover image for I Built a SQL Diagnostics Tool for Developers Who Don't Have a DBA
Sajja Sudhakararao for AutoShiftOps

Posted on • Originally published at querytuner.com

I Built a SQL Diagnostics Tool for Developers Who Don't Have a DBA

The tools that exist for SQL performance are built for DBAs. They cost $400/month, require installing an agent with database credentials, and assume you already know what an execution plan is and what to do with it.

That leaves a gap. Most development teams — especially startups, small engineering teams, and solo developers — don't have a dedicated DBA. When a query is slow, the developer who wrote it is also the person who has to fix it. And the tools available to them are either a generic LLM that doesn't know which database they're on, or an enterprise product they can't afford or access.

I built QueryTuner for that gap.

What it does

Paste a SQL query. Select your database dialect.
Get back in under a second:

  • What's wrong and why — specific patterns causing performance problems, explained in plain English
  • A rewritten query — with slow patterns fixed, dialect-correct syntax
  • The exact index to create — for your specific database, not generic advice
  • A security scan — SQL injection risk patterns
  • A shareable report URL — permanent link to the analysis for PR comments or Slack threads

No database connection. No credentials. No installation.

Why no database connection

This was a deliberate architecture decision, not a limitation.

Every enterprise SQL diagnostic tool requires database credentials. That means a security review before you can even try the tool. In most organizations that review takes longer than fixing the query.

QueryTuner analyzes query syntax and structure. You paste the SQL text — the same text you already have in your editor. No connection string, no firewall rules, no credential approval process.

The tradeoff: without connecting to your database, QueryTuner can't know actual table sizes, row counts, or current index usage. Recommendations are marked estimated unless you provide schema DDL — more on that below.

The dialect problem

SQL is not one language. The correct way to create an index in production differs significantly across databases:

-- PostgreSQL (CONCURRENTLY avoids locking the table)
CREATE INDEX CONCURRENTLY idx_orders_customer_id 
ON orders(customer_id);

-- MySQL (ALTER TABLE is the idiomatic form)
ALTER TABLE orders ADD INDEX 
idx_orders_customer_id (customer_id);

-- Oracle (NOLOGGING skips redo log for faster creation)
CREATE INDEX idx_orders_customer_id 
ON orders(customer_id) NOLOGGING;

-- SQL Server (ONLINE=ON allows reads/writes during build)
CREATE NONCLUSTERED INDEX idx_orders_customer_id 
ON orders(customer_id) WITH (ONLINE=ON, FILLFACTOR=90);

-- SQLite (no concurrent DDL — locks the database file)
CREATE INDEX IF NOT EXISTS idx_orders_customer_id 
ON orders(customer_id);
Enter fullscreen mode Exit fullscreen mode

Generic advice ("add an index on customer_id") is not enough. The DDL you run in production depends on your database. QueryTuner generates the correct statement for whichever of the five supported dialects you select.

What it catches

12 heuristic rules, always running in under 5ms:

High severity:

  • Functions on indexed columns in WHERE clauses — YEAR(created_at) = 2024 forces a full table scan. Rewrite as created_at BETWEEN '2024-01-01' AND '2024-12-31' and the index works again.
  • Correlated subqueries in the SELECT clause — executes once per row of the outer query. On 10,000 rows that's 10,000 separate lookups. A LEFT JOIN is evaluated once.
  • Cartesian JOINs — a JOIN without an ON clause multiplies every row in table A by every row in table B. On production tables this can return millions of rows and crash your database.
  • Leading wildcard LIKE patterns — LIKE '%value' cannot use a B-tree index. Full table scan every time.

Medium severity:

  • SELECT * fetching unnecessary columns
  • ORDER BY without LIMIT on large result sets
  • Implicit type casts preventing index use
  • Missing indexes on JOIN keys, WHERE filters, ORDER BY, GROUP BY columns

Schema-aware confirmed recommendations

The tool works without schema information. But if you paste your CREATE TABLE statements alongside the query, recommendations upgrade from
estimated to confirmed.

Without schema:

ddl_hint: "CREATE INDEX idx_o_customer_id 
           ON <o_table>(customer_id)"
confirmed: false
Enter fullscreen mode Exit fullscreen mode

With schema:

ddl_hint: "CREATE INDEX CONCURRENTLY 
           idx_orders_customer_id 
           ON orders(customer_id)"
confirmed: true
Enter fullscreen mode Exit fullscreen mode

With schema provided, QueryTuner:

  • Resolves table aliases to real table names
  • Verifies the column actually exists
  • Suppresses suggestions for indexes already in your DDL
  • Generates copy-pasteable DDL with your real table names

The LLM layer

When enabled, an optional LLM layer (HuggingFace or OpenAI) adds narrative context on top of the heuristic findings — plain-English diagnosis, a rewritten query using CTEs, and flags for assumptions it can't verify without schema.

The heuristic layer always runs regardless. If the LLM fails (cold start, rate limit), the structured findings are still returned. The LLM is additive, not load-bearing.

Try it

Live: querytuner.com

Source: github.com/AutoShiftOps/querytuner

API example — paste query + schema, get confirmed DDL:

curl -X POST \
  https://sql-query-analyzer-ekbk.onrender.com/analyze \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT u.id, u.name, o.total FROM users u JOIN orders o ON o.customer_id = u.id WHERE u.status = '\''active'\'' ORDER BY o.created_at DESC",
    "db_type": "postgresql",
    "use_llm": false,
    "schema_info": "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status VARCHAR(20));\nCREATE TABLE orders (id SERIAL PRIMARY KEY, customer_id INTEGER NOT NULL, total NUMERIC(10,2), created_at TIMESTAMPTZ DEFAULT NOW());"
  }'
Enter fullscreen mode Exit fullscreen mode

Response includes:

{
  "optimization_suggestions": [
    {
      "type": "index_review_join_key",
      "severity": "high",
      "suggestion": "JOIN key o.customer_id may lack an index",
      "ddl_hint": "CREATE INDEX CONCURRENTLY idx_orders_customer_id ON orders(customer_id);",
      "ddl_note": "Use CONCURRENTLY to avoid locking the table in production.",
      "confirmed": true
    }
  ],
  "plain_explanation": "## Schema Context\n- QueryTuner cross-referenced 3/4 suggestions against schema — 3 confirmed, 1 estimated",
  "share_url": "https://querytuner.com/report/11ac926d-89a9-4e52-ad9c-d14c70d37965"
}
Enter fullscreen mode Exit fullscreen mode

confirmed: true means the column exists in your schema, no index on it appears in your DDL, and the DDL uses your real table name — not a placeholder.

What it doesn't do

Honest about the limitations:

  • No live database connection — can't see actual row counts, current query plans, or buffer cache usage
  • No query history — each analysis is stateless
  • LLM availability depends on HuggingFace free tier — cold starts can add latency
  • LATERAL join correlation not yet detected
  • No stored procedure analysis

Full limitations: LIMITATIONS.md


If you've ever been handed a slow query with no DBA to ask — I'd genuinely value your feedback. Especially from Oracle and SQL Server users where
I have the least real-world testing.


Sudhakar Sajja is an Application Architect at TechMahindra based in Mississauga, Canada. He writes about DevOps, cloud architecture, and AI-powered developer tooling at autoshiftops.com.

Top comments (0)