What Is a Bad SQL Query? (And Why It Slows Everything Down)

ADVERTISEMENT

The Hidden Cost of Queries That “Just Work”

Your SQL returns the correct result. So it’s fine… right?

Not even close.

In real-world systems, a SQL query that works but isn’t tuned is one of the biggest hidden risks. It may work fine during development, but once real data hits — millions of rows, multiple joins, user concurrency — everything slows down.

If you’re not thinking about performance while writing SQL, you’re building time bombs.Let’s look at a single query that does almost all of these wrong.

Why SQL Performance Tuning Matters

Whether you’re a developer, architect, or DBA, SQL tuning isn’t optional. It’s your insurance against scalability disasters.

Here’s what well-tuned SQL delivers:

  • Faster user experience (especially in web and mobile apps)
  • Lower cloud/database costs (by reducing CPU, I/O, and memory usage)
  • Efficient index usage (less full scans, more targeted lookups)
  • More secure and stable systems (less resource contention)
  • Peace of mind in production (no random query meltdowns)

What Makes a SQL Query “Bad”?

A bad SQL query doesn’t throw an error — it does something worse:

It silently kills performance, wastes resources, and slows down your entire system without warning.

These queries might:

  • Work fine during development
  • Return the correct result
  • Pass testing with small datasets

But once they hit real-world volumes, they:

  • Trigger full table scans
  • Bypass indexes
  • Cause row-by-row operations instead of set-based logic
  • Block concurrent sessions
  • Force the optimizer to guess wrong due to poor statistics or structure

In short:

A bad SQL query is one that doesn’t scale.

Let’s break down what makes a query bad — and how to fix it — with real-world examples.

Let’s look at a single query that does almost all of these wrong.

Bad SQL Query Example

-- ❌ BAD QUERY: Loaded with performance issues
SELECT DISTINCT *
FROM orders o
JOIN customers c ON TO_CHAR(o.customer_id) = c.customer_id     -- ❌ Implicit conversion
JOIN order_items oi ON o.order_id = oi.order_id(+)             -- ❌ Deprecated join syntax
LEFT JOIN inventory inv ON oi.product_id = inv.product_id      -- ❌ Unfiltered join
WHERE o.status = 'completed'
  AND UPPER(o.region) = 'WEST'                                 -- ❌ Function on column
  AND o.order_date + 1 >= SYSDATE                              -- ❌ Arithmetic on column
  AND o.total_amount > 1000
  AND NVL(o.promo_code, 'N/A') = 'SUMMER50'                    -- ❌ Function disables index
  AND oi.product_id IN (
        SELECT p.product_id 
        FROM products p 
        WHERE p.category LIKE '%Elect%'                        -- ❌ Leading wildcard
          AND p.status = 'active'
    )
  AND oi.price * oi.quantity > 500                             -- ❌ Expression disables index
  AND c.country = 'US'
  AND ROWNUM < 100                                             -- ❌ Pagination before sorting
ORDER BY o.order_date DESC;                                    -- ❌ ORDER BY after ROWNUM

✅ The Optimized Version — Clean, Tuned, and Scalable

Here’s the tuned version, using best practices for Oracle performance:

-- ✅ GOOD QUERY: Follows tuning principles
SELECT o.order_id, o.customer_id, o.order_date, o.status, o.region, o.total_amount, oi.product_id
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
LEFT JOIN inventory inv ON oi.product_id = inv.product_id AND inv.stock > 0  -- ✅ Filter at join level
WHERE o.status = 'completed'
  AND o.region = 'WEST'                                -- ✅ Sargable condition
  AND o.order_date >= TRUNC(SYSDATE) - 1               -- ✅ Index-compatible date filter
  AND o.total_amount > 1000
  AND o.promo_code = 'SUMMER50'                        -- ✅ Avoids NVL
  AND EXISTS (                                         -- ✅ EXISTS > IN for large subqueries
        SELECT 1 
        FROM products p 
        WHERE p.product_id = oi.product_id 
          AND p.category LIKE 'Elect%'                 -- ✅ No leading %
          AND p.status = 'active'
    )
  AND oi.price * oi.quantity > 500                     -- ✅ Consider function-based index
  AND c.country = 'US'
ORDER BY o.order_date DESC
FETCH FIRST 100 ROWS ONLY;                             -- ✅ Proper pagination

Checklist for Writing High-Performance SQL Queries in Oracle

1. Index Awareness

  • Write “sargable” queries — avoid functions on indexed columns (UPPER(col), TO_CHAR(col), col + 1, etc.)
  • Avoid expressions on indexed columns in WHERE or JOIN clauses
  • Use equality or range comparisons (=, <, >, BETWEEN) — they work well with B-tree indexes
  • Understand which indexes exist (and how they’re used in your execution plan)
  • ✅ Use function-based indexes if functions on columns are unavoidable

2. Joins & Subqueries

  • Choose the right join type (INNER, LEFT, SEMI, ANTI, etc.)
  • Filter as early as possible in joins (add join predicates carefully)
  • Avoid Cartesian joins unless intentional
  • Use EXISTS instead of IN for correlated subqueries (especially when inner table is large)
  • ✅ Avoid NOT IN with NULLs — use NOT EXISTS or ANTI JOIN
  • ✅ Avoid joining unnecessary tables or views “just in case”

3. WHERE Clause Best Practices

  • Avoid functions on column side of condition: WHERE TRUNC(order_date) = ... → use BETWEEN or >= TRUNC(...)
  • Avoid implicit data type conversions: WHERE id = '123' (when id is NUMBER) → slows query
  • Use bind variables to prevent hard parsing & plan cache pollution
  • ✅ Use IS NULL and IS NOT NULL appropriately (NULL handling is tricky in Oracle)

4. Query Structure & Output

  • Avoid SELECT * — select only the columns you need
  • Avoid unnecessary DISTINCT — often used to mask join logic errors
  • Avoid nested views and inline views unless needed
  • ✅ Use WITH clause (CTEs) to simplify and clarify complex logic — but not blindly

5. Sorting, Pagination, and Aggregation

  • ✅ Use ORDER BY only when needed — it’s expensive!
  • ✅ Use FETCH FIRST N ROWS ONLY instead of ROWNUM for consistent pagination
  • ✅ Use GROUP BY and HAVING with care — can force sort or hash operations
  • ✅ For large result sets, avoid client-side sorting or filtering

6. Plan Visibility & Testing

  • ✅ Always review the execution plan (EXPLAIN PLAN, DBMS_XPLAN, or SQL Monitor)
  • ✅ Use AUTOTRACE or SQL Developer to estimate cost and row counts
  • ✅ Check cardinality estimates — Oracle can be wrong with skewed data
  • ✅ Use STATS and GATHER_STATS properly — bad stats = bad plans

7. Anti-Patterns to Avoid

Anti-Pattern / Problem💥 ImpactBetter Practice
SELECT *Increases I/O and memory usageSelect only the columns you need
TO_CHAR(col) = '123' or UPPER(col) = 'ABC'Prevents index usage (non-sargable)Use consistent data types and avoid functions on columns
NVL(col, 'X') = 'Y' or COALESCE(col, ...)Indexes are bypassedRewrite logic to avoid functions on indexed columns
IN (SELECT ...) with large subqueriesSlow execution due to nested loopingUse EXISTS or refactor into a join
LIKE '%term'Index cannot be usedAvoid leading %, or use full-text search
Implicit data type conversion (e.g., id = '123' when id is NUMBER)Causes full table scansEnsure column and filter value have the same data type
Arithmetic on columns (col + 1 >= value)Disables index range scanRewrite the logic to keep column side clean
ROWNUM < N without ORDER BYUnpredictable and inconsistent paginationUse FETCH FIRST N ROWS ONLY with a proper ORDER BY
Using DISTINCT to fix duplicatesAdds sorting overhead, masks deeper issuesFix join logic to avoid unnecessary duplication
Unfiltered or unnecessary joinsBrings in extra rows, increases temp usageFilter early and avoid joining unused tables
Legacy outer joins using (+)Can be confusing and error-proneUse ANSI SQL (LEFT JOIN, RIGHT JOIN)
Blind use of views or nested viewsHidden performance costsUnderstand and profile the underlying SQL

Golden Rules to Remember

  • Write for the data volume of tomorrow, not today
  • You’re writing instructions for the optimizer, not just pulling data
  • “It works” is not enough — it must scale
  • Never assume Oracle will “figure it out” — guide it with good structure

📌 Final Thoughts

❝ Good SQL is not about writing queries that work. It’s about writing queries that scale. ❞

Bad SQL doesn’t break your application — it slowly bleeds it.
By the time users complain, it’s already hurting your performance, your cost, and your reputation.

By following the principles in this post, you’ll move from just writing queries to building data pipelines and applications that perform under pressure.

ADVERTISEMENT