{"id":4344,"date":"2025-06-18T09:30:00","date_gmt":"2025-06-18T09:30:00","guid":{"rendered":"https:\/\/w3buddy.com\/?post_type=cposts&#038;p=4344"},"modified":"2025-06-20T06:59:52","modified_gmt":"2025-06-20T06:59:52","slug":"what-is-a-bad-sql-query-and-why-it-slows-everything-down","status":"publish","type":"cposts","link":"https:\/\/w3buddy.com\/blog\/notes\/performance-tuning\/what-is-a-bad-sql-query-and-why-it-slows-everything-down\/","title":{"rendered":"What Is a Bad SQL Query? (And Why It Slows Everything Down)"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">The Hidden Cost of Queries That \u201cJust Work\u201d<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Your SQL returns the correct result. So it\u2019s fine&#8230; right?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Not even close.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In real-world systems, a SQL query that <em>works<\/em> but isn\u2019t <em>tuned<\/em> is one of the biggest hidden risks. It may work fine during development, but once real data hits \u2014 <strong>millions of rows<\/strong>, multiple joins, user concurrency \u2014 everything slows down.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you\u2019re not thinking about performance while writing SQL, you\u2019re building time bombs.Let\u2019s look at a single query that does <strong>almost all<\/strong> of these wrong.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why SQL Performance Tuning Matters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Whether you&#8217;re a developer, architect, or DBA, SQL tuning isn&#8217;t optional. It&#8217;s your insurance against scalability disasters.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s what well-tuned SQL delivers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Faster user experience<\/strong> (especially in web and mobile apps)<\/li>\n\n\n\n<li><strong>Lower cloud\/database costs<\/strong> (by reducing CPU, I\/O, and memory usage)<\/li>\n\n\n\n<li><strong>Efficient index usage<\/strong> (less full scans, more targeted lookups)<\/li>\n\n\n\n<li><strong>More secure and stable systems<\/strong> (less resource contention)<\/li>\n\n\n\n<li><strong>Peace of mind in production<\/strong> (no random query meltdowns)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">What Makes a SQL Query \u201cBad\u201d?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A <strong>bad SQL query<\/strong> doesn\u2019t throw an error \u2014 it does something worse:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">It silently <strong>kills performance<\/strong>, <strong>wastes resources<\/strong>, and <strong>slows down your entire system<\/strong> without warning.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">These queries might:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Work fine during development<\/li>\n\n\n\n<li>Return the correct result<\/li>\n\n\n\n<li>Pass testing with small datasets<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">But once they hit real-world volumes, they:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Trigger <strong>full table scans<\/strong><\/li>\n\n\n\n<li><strong>Bypass indexes<\/strong><\/li>\n\n\n\n<li>Cause <strong>row-by-row operations<\/strong> instead of set-based logic<\/li>\n\n\n\n<li><strong>Block concurrent sessions<\/strong><\/li>\n\n\n\n<li>Force the optimizer to guess wrong due to <strong>poor statistics or structure<\/strong><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In short:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>A bad SQL query is one that doesn\u2019t scale.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s break down what makes a query bad \u2014 and how to fix it \u2014 with real-world examples.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s look at a single query that does <strong>almost all<\/strong> of these wrong.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Bad SQL Query Example<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- \u274c BAD QUERY: Loaded with performance issues\nSELECT DISTINCT *\nFROM orders o\nJOIN customers c ON TO_CHAR(o.customer_id) = c.customer_id     -- \u274c Implicit conversion\nJOIN order_items oi ON o.order_id = oi.order_id(+)             -- \u274c Deprecated join syntax\nLEFT JOIN inventory inv ON oi.product_id = inv.product_id      -- \u274c Unfiltered join\nWHERE o.status = 'completed'\n  AND UPPER(o.region) = 'WEST'                                 -- \u274c Function on column\n  AND o.order_date + 1 &gt;= SYSDATE                              -- \u274c Arithmetic on column\n  AND o.total_amount &gt; 1000\n  AND NVL(o.promo_code, 'N\/A') = 'SUMMER50'                    -- \u274c Function disables index\n  AND oi.product_id IN (\n        SELECT p.product_id \n        FROM products p \n        WHERE p.category LIKE '%Elect%'                        -- \u274c Leading wildcard\n          AND p.status = 'active'\n    )\n  AND oi.price * oi.quantity &gt; 500                             -- \u274c Expression disables index\n  AND c.country = 'US'\n  AND ROWNUM &lt; 100                                             -- \u274c Pagination before sorting\nORDER BY o.order_date DESC;                                    -- \u274c ORDER BY after ROWNUM<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 The Optimized Version \u2014 Clean, Tuned, and Scalable<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the tuned version, using <strong>best practices<\/strong> for Oracle performance:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- \u2705 GOOD QUERY: Follows tuning principles\nSELECT o.order_id, o.customer_id, o.order_date, o.status, o.region, o.total_amount, oi.product_id\nFROM orders o\nJOIN customers c ON o.customer_id = c.customer_id\nLEFT JOIN order_items oi ON o.order_id = oi.order_id\nLEFT JOIN inventory inv ON oi.product_id = inv.product_id AND inv.stock &gt; 0  -- \u2705 Filter at join level\nWHERE o.status = 'completed'\n  AND o.region = 'WEST'                                -- \u2705 Sargable condition\n  AND o.order_date &gt;= TRUNC(SYSDATE) - 1               -- \u2705 Index-compatible date filter\n  AND o.total_amount &gt; 1000\n  AND o.promo_code = 'SUMMER50'                        -- \u2705 Avoids NVL\n  AND EXISTS (                                         -- \u2705 EXISTS &gt; IN for large subqueries\n        SELECT 1 \n        FROM products p \n        WHERE p.product_id = oi.product_id \n          AND p.category LIKE 'Elect%'                 -- \u2705 No leading %\n          AND p.status = 'active'\n    )\n  AND oi.price * oi.quantity &gt; 500                     -- \u2705 Consider function-based index\n  AND c.country = 'US'\nORDER BY o.order_date DESC\nFETCH FIRST 100 ROWS ONLY;                             -- \u2705 Proper pagination<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 <strong>Checklist for Writing High-Performance SQL Queries in Oracle<\/strong><\/h3>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>1. Index Awareness<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 <strong>Write \u201csargable\u201d queries<\/strong> \u2014 avoid functions on indexed columns (<code>UPPER(col)<\/code>, <code>TO_CHAR(col)<\/code>, <code>col + 1<\/code>, etc.)<\/li>\n\n\n\n<li>\u2705 <strong>Avoid expressions on indexed columns<\/strong> in <code>WHERE<\/code> or <code>JOIN<\/code> clauses<\/li>\n\n\n\n<li>\u2705 <strong>Use equality or range comparisons<\/strong> (<code>=<\/code>, <code>&lt;<\/code>, <code>&gt;<\/code>, <code>BETWEEN<\/code>) \u2014 they work well with B-tree indexes<\/li>\n\n\n\n<li>\u2705 <strong>Understand which indexes exist<\/strong> (and how they\u2019re used in your execution plan)<\/li>\n\n\n\n<li>\u2705 Use <strong>function-based indexes<\/strong> if functions on columns are unavoidable<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>2. Joins &amp; Subqueries<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 <strong>Choose the right join type<\/strong> (<code>INNER<\/code>, <code>LEFT<\/code>, <code>SEMI<\/code>, <code>ANTI<\/code>, etc.)<\/li>\n\n\n\n<li>\u2705 <strong>Filter as early as possible<\/strong> in joins (add join predicates carefully)<\/li>\n\n\n\n<li>\u2705 <strong>Avoid Cartesian joins<\/strong> unless intentional<\/li>\n\n\n\n<li>\u2705 <strong>Use <code>EXISTS<\/code> instead of <code>IN<\/code><\/strong> for correlated subqueries (especially when inner table is large)<\/li>\n\n\n\n<li>\u2705 Avoid <code>NOT IN<\/code> with <code>NULL<\/code>s \u2014 use <code>NOT EXISTS<\/code> or <code>ANTI JOIN<\/code><\/li>\n\n\n\n<li>\u2705 Avoid joining unnecessary tables or views \u201cjust in case\u201d<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>3. WHERE Clause Best Practices<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 <strong>Avoid functions on column side<\/strong> of condition: <code>WHERE TRUNC(order_date) = ...<\/code> \u2192 use <code>BETWEEN<\/code> or <code>&gt;= TRUNC(...)<\/code><\/li>\n\n\n\n<li>\u2705 <strong>Avoid implicit data type conversions<\/strong>: <code>WHERE id = '123'<\/code> (when <code>id<\/code> is <code>NUMBER<\/code>) \u2192 slows query<\/li>\n\n\n\n<li>\u2705 <strong>Use bind variables<\/strong> to prevent hard parsing &amp; plan cache pollution<\/li>\n\n\n\n<li>\u2705 Use <code>IS NULL<\/code> and <code>IS NOT NULL<\/code> appropriately (NULL handling is tricky in Oracle)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>4. Query Structure &amp; Output<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 <strong>Avoid <code>SELECT *<\/code><\/strong> \u2014 select only the columns you need<\/li>\n\n\n\n<li>\u2705 <strong>Avoid unnecessary <code>DISTINCT<\/code><\/strong> \u2014 often used to mask join logic errors<\/li>\n\n\n\n<li>\u2705 <strong>Avoid nested views and inline views unless needed<\/strong><\/li>\n\n\n\n<li>\u2705 Use <code>WITH<\/code> clause (CTEs) to simplify and clarify complex logic \u2014 but not blindly<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>5. Sorting, Pagination, and Aggregation<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 Use <code>ORDER BY<\/code> only when needed \u2014 it&#8217;s expensive!<\/li>\n\n\n\n<li>\u2705 Use <code>FETCH FIRST N ROWS ONLY<\/code> instead of <code>ROWNUM<\/code> for consistent pagination<\/li>\n\n\n\n<li>\u2705 Use <code>GROUP BY<\/code> and <code>HAVING<\/code> with care \u2014 can force sort or hash operations<\/li>\n\n\n\n<li>\u2705 For large result sets, avoid client-side sorting or filtering<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>6. Plan Visibility &amp; Testing<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 Always <strong>review the execution plan<\/strong> (<code>EXPLAIN PLAN<\/code>, <code>DBMS_XPLAN<\/code>, or <code>SQL Monitor<\/code>)<\/li>\n\n\n\n<li>\u2705 Use <code>AUTOTRACE<\/code> or <code>SQL Developer<\/code> to estimate cost and row counts<\/li>\n\n\n\n<li>\u2705 Check cardinality estimates \u2014 Oracle can be wrong with skewed data<\/li>\n\n\n\n<li>\u2705 Use <code>STATS<\/code> and <code>GATHER_STATS<\/code> properly \u2014 bad stats = bad plans<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>7. Anti-Patterns to Avoid<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-table has-small-font-size\"><table><thead><tr><th>\u274c <strong>Anti-Pattern \/ Problem<\/strong><\/th><th>\ud83d\udca5 <strong>Impact<\/strong><\/th><th>\u2705 <strong>Better Practice<\/strong><\/th><\/tr><\/thead><tbody><tr><td><code>SELECT *<\/code><\/td><td>Increases I\/O and memory usage<\/td><td>Select only the columns you need<\/td><\/tr><tr><td><code>TO_CHAR(col) = '123'<\/code> or <code>UPPER(col) = 'ABC'<\/code><\/td><td>Prevents index usage (non-sargable)<\/td><td>Use consistent data types and avoid functions on columns<\/td><\/tr><tr><td><code>NVL(col, 'X') = 'Y'<\/code> or <code>COALESCE(col, ...)<\/code><\/td><td>Indexes are bypassed<\/td><td>Rewrite logic to avoid functions on indexed columns<\/td><\/tr><tr><td><code>IN (SELECT ...)<\/code> with large subqueries<\/td><td>Slow execution due to nested looping<\/td><td>Use <code>EXISTS<\/code> or refactor into a join<\/td><\/tr><tr><td><code>LIKE '%term'<\/code><\/td><td>Index cannot be used<\/td><td>Avoid leading <code>%<\/code>, or use full-text search<\/td><\/tr><tr><td>Implicit data type conversion (e.g., <code>id = '123'<\/code> when <code>id<\/code> is NUMBER)<\/td><td>Causes full table scans<\/td><td>Ensure column and filter value have the same data type<\/td><\/tr><tr><td>Arithmetic on columns (<code>col + 1 &gt;= value<\/code>)<\/td><td>Disables index range scan<\/td><td>Rewrite the logic to keep column side clean<\/td><\/tr><tr><td><code>ROWNUM &lt; N<\/code> without <code>ORDER BY<\/code><\/td><td>Unpredictable and inconsistent pagination<\/td><td>Use <code>FETCH FIRST N ROWS ONLY<\/code> with a proper <code>ORDER BY<\/code><\/td><\/tr><tr><td>Using <code>DISTINCT<\/code> to fix duplicates<\/td><td>Adds sorting overhead, masks deeper issues<\/td><td>Fix join logic to avoid unnecessary duplication<\/td><\/tr><tr><td>Unfiltered or unnecessary joins<\/td><td>Brings in extra rows, increases temp usage<\/td><td>Filter early and avoid joining unused tables<\/td><\/tr><tr><td>Legacy outer joins using <code>(+)<\/code><\/td><td>Can be confusing and error-prone<\/td><td>Use ANSI SQL (<code>LEFT JOIN<\/code>, <code>RIGHT JOIN<\/code>)<\/td><\/tr><tr><td>Blind use of views or nested views<\/td><td>Hidden performance costs<\/td><td>Understand and profile the underlying SQL<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 <strong>Golden Rules to Remember<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Write for the data volume of tomorrow<\/strong>, not today<\/li>\n\n\n\n<li><strong>You\u2019re writing instructions for the optimizer<\/strong>, not just pulling data<\/li>\n\n\n\n<li><strong>&#8220;It works&#8221; is not enough \u2014 it must scale<\/strong><\/li>\n\n\n\n<li><strong>Never assume Oracle will &#8220;figure it out&#8221;<\/strong> \u2014 guide it with good structure<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udccc Final Thoughts<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u275d Good SQL is not about writing queries that work. It&#8217;s about writing queries that scale. \u275e<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Bad SQL doesn&#8217;t break your application \u2014 it slowly bleeds it.<br>By the time users complain, it&#8217;s already hurting your performance, your cost, and your reputation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By following the principles in this post, you\u2019ll move from <em>just writing queries<\/em> to building <strong>data pipelines and applications that perform under pressure.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Hidden Cost of Queries That \u201cJust Work\u201d Your SQL returns the correct result. So it\u2019s fine&#8230; right? Not even close. In real-world systems, a SQL query that works but isn\u2019t tuned is one of the biggest hidden risks. It may work fine during development, but once real data hits \u2014 millions of rows, multiple [&hellip;]<\/p>\n","protected":false},"template":"","meta":{"googlesitekit_rrm_CAowu461DA:productID":""},"categories":[952,992],"class_list":["post-4344","cposts","type-cposts","status-publish","hentry","category-notes","category-performance-tuning"],"_links":{"self":[{"href":"https:\/\/w3buddy.com\/blog\/wp-json\/wp\/v2\/cposts\/4344","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/w3buddy.com\/blog\/wp-json\/wp\/v2\/cposts"}],"about":[{"href":"https:\/\/w3buddy.com\/blog\/wp-json\/wp\/v2\/types\/cposts"}],"wp:attachment":[{"href":"https:\/\/w3buddy.com\/blog\/wp-json\/wp\/v2\/media?parent=4344"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/w3buddy.com\/blog\/wp-json\/wp\/v2\/categories?post=4344"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}