SQL is one of the most widely used languages in software development, data engineering, analytics, and business intelligence. Whether you’re querying a data warehouse, building an ETL pipeline, or powering a web application, well-written SQL is essential for delivering reliable and efficient systems.
Unfortunately, many SQL queries that appear to work correctly can become major performance bottlenecks as data grows. A query that runs in milliseconds on a small development database may take several minutes—or even hours—when executed against millions of rows in production.
These inefficient patterns are known as SQL anti-patterns. They may not produce errors, but they often result in slower queries, unnecessary resource consumption, and difficult-to-maintain code.
In this guide, you’ll learn the most common SQL query anti-patterns, why they cause problems, and practical techniques for writing faster and more scalable SQL.
What Is a SQL Anti-Pattern?
A SQL anti-pattern is a design or coding practice that appears to solve a problem but introduces unnecessary inefficiency or complexity.
Examples include:
- Retrieving unnecessary columns
- Using functions that prevent index usage
- Writing inefficient joins
- Repeating expensive subqueries
- Ignoring execution plans
Many anti-patterns don’t become noticeable until datasets grow significantly.
SQL query anti-patterns are common coding practices that negatively impact performance, scalability, readability, or maintainability. Avoiding them helps databases execute queries more efficiently and makes SQL easier to understand and maintain.
Why SQL Anti-Patterns Matter
Poor SQL affects more than individual queries.
It can lead to:
- Slow dashboards
- Higher cloud database costs
- Increased CPU usage
- Longer ETL jobs
- Poor application response times
- Resource contention
- Difficult maintenance
Optimizing SQL improves both performance and operational efficiency.
Anti-Pattern 1: Using SELECT *
One of the most common mistakes is retrieving every column when only a few are needed.
Instead of:
SELECT *
FROM customers;
Prefer:
SELECT customer_id,
first_name,
email
FROM customers;
Why it’s a problem
SELECT *:
- Reads unnecessary data
- Increases network traffic
- Prevents column pruning
- Makes queries less predictable when schemas change
Always retrieve only the columns you actually need.
Anti-Pattern 2: Filtering with Functions
Applying functions directly to indexed columns often prevents the database from using indexes efficiently.
Instead of:
SELECT *
FROM orders
WHERE YEAR(order_date) = 2025;
Prefer:
SELECT *
FROM orders
WHERE order_date >= '2025-01-01'
AND order_date < '2026-01-01';
Why it’s a problem
Wrapping indexed columns in functions frequently forces a full table scan instead of an index seek.
Anti-Pattern 3: Unnecessary Nested Subqueries
Deeply nested queries are harder to optimize and maintain.
Instead of:
SELECT *
FROM (
SELECT *
FROM customers
) c;
Simplify the query whenever possible.
Modern SQL optimizers are powerful, but unnecessary nesting still reduces readability.
Anti-Pattern 4: Using DISTINCT to Hide Data Problems
Many developers add DISTINCT because duplicate rows appear unexpectedly.
SELECT DISTINCT customer_id
FROM orders;
This may hide:
- Incorrect joins
- Duplicate source data
- Modeling issues
Rather than masking the symptom, investigate the underlying cause.
Anti-Pattern 5: Missing Join Conditions
Forgetting a join condition creates a Cartesian product.
SELECT *
FROM customers
JOIN orders;
Every customer is matched with every order, potentially producing millions of unnecessary rows.
Always define explicit join conditions.
ON customers.customer_id = orders.customer_id
Anti-Pattern 6: Using LIKE '%text%' Everywhere
Leading wildcards reduce index effectiveness.
WHERE product_name LIKE '%Laptop%'
For large text datasets, consider:
- Full-text search
- Search indexes
- Dedicated search engines
These solutions scale much better.
Anti-Pattern 7: Repeating the Same Subquery
Repeated subqueries perform the same work multiple times.
Instead, use:
- Common Table Expressions (CTEs)
- Temporary tables
- Materialized views (where appropriate)
This improves readability and may reduce repeated computation.
Anti-Pattern 8: Ignoring NULL Handling
NULL behaves differently from ordinary values.
Incorrect:
WHERE discount = NULL
Correct:
WHERE discount IS NULL
Improper NULL handling often produces unexpected query results.
Anti-Pattern 9: Storing Comma-Separated Values
Example:
skills
-----------------------
Python,SQL,Excel
This violates database normalization.
Problems include:
- Difficult searching
- Poor indexing
- Data inconsistencies
Instead, create a related table:
employee_skills
---------------
employee_id
skill
This structure is easier to query and maintain.
Anti-Pattern 10: Ignoring Indexes
Indexes significantly improve query performance.
Queries filtering large tables should generally use indexed columns where appropriate.
However, avoid excessive indexing because every additional index increases write overhead.
Balance read and write performance based on workload.
Anti-Pattern 11: Returning Huge Result Sets
Avoid retrieving millions of rows when only a subset is needed.
Use:
- Pagination
- Filtering
- Aggregation
- Sampling
This reduces memory usage and network transfer.
Anti-Pattern 12: Not Reviewing Execution Plans
The SQL query may look efficient while the database executes it poorly.
Execution plans reveal:
- Table scans
- Index usage
- Join strategies
- Estimated costs
- Bottlenecks
Learning to read execution plans is one of the most valuable SQL optimization skills.
Common Performance Optimization Tips
Follow these practices to improve query performance:
- Retrieve only required columns.
- Filter data as early as possible.
- Create appropriate indexes.
- Avoid unnecessary sorting.
- Prefer set-based operations over row-by-row processing.
- Review execution plans regularly.
- Keep statistics up to date.
- Optimize joins before optimizing hardware.
Real-World Example
Imagine an e-commerce dashboard that queries:
- 100 million orders
- 20 million customers
- 5 million products
Using SELECT *, unindexed filters, and unnecessary DISTINCT statements could increase execution time from seconds to several minutes.
After:
- selecting only required columns,
- rewriting date filters,
- adding appropriate indexes, and
- simplifying joins,
the same query may execute dramatically faster while consuming fewer database resources.
Best Practices
Write Readable SQL
Clear formatting and meaningful aliases improve maintainability.
Optimize After Measuring
Focus optimization efforts on queries identified through monitoring and execution plans rather than guessing.
Use Appropriate Indexes
Create indexes that match common filtering and join patterns, and review them as workloads evolve.
Keep Queries Simple
Straightforward SQL is generally easier to optimize and troubleshoot than overly complex queries.
Test with Production-Sized Data
Performance characteristics can change significantly as datasets grow.
Common Mistakes
Assuming Fast Development Queries Will Scale
Queries that perform well on small datasets may become bottlenecks in production.
Overusing DISTINCT
DISTINCT should solve a business requirement—not compensate for incorrect joins or poor data quality.
Ignoring Database Statistics
Outdated statistics can lead the query optimizer to choose inefficient execution plans.
Optimizing Too Early
Prioritize the queries that consume the most time or resources instead of optimizing every query equally.
The Future of SQL Optimization
Modern databases continue to improve query optimization through adaptive execution, automatic indexing, AI-assisted tuning, and cloud-native scaling. Even with these advances, writing efficient SQL remains essential. Good query design allows database optimizers to perform their best work while reducing costs and improving user experience.
As organizations process larger datasets and build increasingly complex analytics platforms, understanding SQL anti-patterns will remain a valuable skill for data engineers, analytics engineers, software developers, and database administrators.
SQL anti-patterns often begin as small shortcuts, but they can become serious performance and maintenance issues as applications scale. By avoiding common mistakes such as using SELECT *, hiding problems with DISTINCT, applying functions to indexed columns, and ignoring execution plans, engineers can build faster, more reliable, and more maintainable SQL solutions.
Whether you’re writing ETL pipelines, analytics queries, or application backends, recognizing and eliminating these anti-patterns is one of the most effective ways to improve database performance.
FAQ
What is a SQL anti-pattern?
A SQL anti-pattern is a coding practice that negatively affects query performance, scalability, readability, or maintainability.
Why should I avoid SELECT *?
It retrieves unnecessary columns, increases I/O and network traffic, and makes queries more fragile when table schemas change.
Why are execution plans important?
Execution plans show how the database executes a query, helping identify bottlenecks such as table scans, expensive joins, and missing indexes.
Does DISTINCT improve performance?
Not necessarily. While it removes duplicate rows, it can also add extra processing and may hide underlying data modeling or join issues.
Should every query use indexes?
No. Indexes improve many read operations but increase storage requirements and slow inserts, updates, and deletes. Use indexes strategically based on query patterns.