Skip to main content
Transact-SQL (T-SQL), despite being a powerful extension to SQL for Microsoft SQL Server, has several common anti-patterns that can lead to performance issues, security vulnerabilities, and maintainability problems. Here are the most important anti-patterns to avoid when writing T-SQL code.
Avoid using SELECT * in production code. Instead, explicitly list the columns you need. This improves performance by reducing I/O and network traffic, prevents issues when table schemas change, and makes your code more maintainable by clearly showing which columns are being used.
Always use parameterized queries instead of concatenating strings to build SQL queries. This prevents SQL injection attacks and improves performance through query plan caching.
Avoid using the NOLOCK hint (or its equivalent, READUNCOMMITTED) indiscriminately. It can lead to dirty reads, non-repeatable reads, phantom reads, and even missing or duplicate rows due to reading data that is being modified. Use appropriate isolation levels based on your specific requirements.
Avoid using cursors for row-by-row processing when set-based operations can accomplish the same task. Set-based operations are typically much faster and more efficient in SQL Server.
Use temporary tables (#temp) instead of table variables (@table) for large result sets or when you need indexes. Table variables store statistics only when they’re created and don’t support indexes (prior to SQL Server 2014), which can lead to poor query plans for large datasets.
Avoid using dynamic SQL when static SQL would suffice. Dynamic SQL prevents the query optimizer from caching execution plans, can introduce security vulnerabilities if not properly parameterized, and makes code harder to read and maintain.
Use SET-based UPDATE and DELETE operations with JOIN syntax instead of subqueries in the WHERE clause. This is often more efficient and readable, especially for complex conditions.
Avoid using functions on columns in WHERE clauses, especially on indexed columns. This prevents the query optimizer from using indexes effectively. Rewrite your queries to apply conditions directly to the columns.
Always use schema names when referencing database objects. This improves query plan caching, prevents ambiguity if multiple schemas have objects with the same name, and makes your code more maintainable.
Avoid using randomly generated GUIDs (NEWID()) as clustered index keys. They cause index fragmentation and poor performance due to random inserts. Use identity columns, sequential GUIDs (NEWSEQUENTIALID()), or make the GUID a non-clustered index.
Use appropriate data types for your columns. Using the wrong data type can lead to unexpected behavior, poor performance, and increased storage requirements. For example, use numeric types for numbers, date/time types for dates, and VARCHAR only for variable-length strings.
Avoid using DISTINCT as a quick fix for duplicate rows. It can hide join problems and hurt performance. Instead, fix the underlying issue by using the correct join type or restructuring your query.
Use Common Table Expressions (CTEs) to simplify complex queries, improve readability, and avoid repeating subqueries. CTEs can also be recursive, which is useful for hierarchical data.
Use EXISTS instead of COUNT() to check if records exist. EXISTS is more efficient because it stops scanning as soon as it finds a matching record, while COUNT() needs to scan all matching records.
Avoid using OFFSET/FETCH or ROW_NUMBER() for pagination with large offset values. These methods require the database to scan and discard all rows up to the offset, which becomes increasingly inefficient as the offset grows. Use keyset pagination instead, which uses a filter on the last seen value.
Use appropriate constraints (PRIMARY KEY, FOREIGN KEY, NOT NULL, UNIQUE, CHECK) to enforce data integrity at the database level. This prevents invalid data from being inserted and makes your database more reliable.
Avoid implicit conversions between different data types. They can prevent the optimizer from using indexes and lead to unexpected results. Use the correct data type in your queries or explicit conversions when necessary.
Develop an appropriate indexing strategy based on your query patterns. Create indexes on columns used in WHERE clauses, JOIN conditions, and ORDER BY statements. Consider covering indexes for frequently used queries.
Use the MERGE statement with caution. It has known issues in some versions of SQL Server and can lead to unexpected results if not carefully implemented. Always include explicit conditions for WHEN MATCHED clauses and consider using separate INSERT, UPDATE, and DELETE statements for complex operations.
Use SET NOCOUNT ON in stored procedures and triggers to prevent sending row count messages to the client after each statement. This reduces network traffic and can improve performance, especially for procedures with multiple statements.
Avoid using LIKE with leading wildcards (‘%text’) in queries on large tables. This forces a full table scan and can’t use indexes effectively. Consider using full-text search features for better performance with text searching.