- SQL Performance (0), Summary
- SQL Performance (1), Optimized SELECT Query (A)
- SQL Performance (2), Optimized SELECT Query (B) --- this article
- SQL Performance (3), Optimized Data Modifying Query
- SQL Performance (4), Tune the SQL Server Database
A: Introduction
B: Tips for Improvement of Tuning Query
- Create joins with INNER JOIN (not WHERE --- Cross Join)
- Avoid Multiple Joins in a Single Query
- Avoid using table variables in joins
- Using Temp Table wisely
- Using a Subquery
- Using a CTE
- Avoid Correlated SQL Subqueries
- Avoid Multi-Statement Table-Valued Functions (TVFs), instead using inline TVFs
- Avoid using wildcard characters at the beginning of the LIKE pattern
- Avoid using COUNT(), instead of Exist()
- Avoid Using GROUP BY, ORDER BY, and DISTINCT
- Avoid Different Datatype on JOIN and WHERE Conditions
- Avoid using HAVING to define a filter, instead of WHERE
- Avoid OR conditions
- Avoid sorting with a mixed order
SELECT Customers.CustomerID, Customers.Name, Sales.LastSaleDate
FROM Customers, Sales
WHERE Customers.CustomerID = Sales.CustomerID
This type of join creates a Cartesian Join, also called a Cartesian Product or CROSS JOIN.
SELECT Customers.CustomerID, Customers.Name, Sales.LastSaleDate
FROM Customers INNER JOIN Sales
ON Customers.CustomerID = Sales.CustomerID
The database would only generate the 1,000 desired records where CustomerID is equal.
SELECT * FROM tag
JOIN tag_post ON tag_post.tag_id = tag.id
JOIN post ON tag_post.post_id = post.id
WHERE tag.tag = 'mysql';
You might run these queries,
SELECT * FROM tag WHERE tag = 'mysql';
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
Why do this? It looks wasteful at first glance because you've increased the number of queries without getting anything in return. However, such restructuring can actually give significant performance advantages,
- Caching can be more efficient. Many applications cache "objects" that map directly to tables. In this example, if the object with the tag
mysqlis already cached, the application will skip the first query. If you find posts with an ID of 123, 567, or 908 in the cache, you can remove them from theIN()list. The query cache might also benefit from this strategy. If only one of the tables changes frequently, decomposing a join can reduce the number of cache invalidations. - Executing the queries individually can sometimes reduce lock contention
- The queries themselves can be more efficient. In this example, using a
IN()list instead of a join lets MySQL sort row IDs and retrieve rows more optimally than might be possible with a join. - You can reduce redundant row accesses. Doing a join in the application means retrieving each row only once., whereas a join in the query is essentially denormalization that might repeatedly access the same data. For the same reason, such restructuring might also reduce the total network traffic and memory usage.
- To some extent, you can view this technique as manually implementing a hash join instead of the nested loops algorithm MySQL uses to execute a join. A hash join might be more efficient.
IN() lists or a join refers to the same table multiple times.SELECT INTO statement and then joining with the temp table,
SELECT * INTO #Temp FROM Customer WHERE RegionID = 5
SELECT r.RegionName, t.Name FROM Region r JOIN #Temp t ON t.RegionID = r.RegionID
(Note: some SQL developers also avoid using SELECT INTO to create temp tables, saying that this command locks the tempdb database, disallowing other users from creating temp tables. Fortunately, this is fixed in 7.0 and later.)
SELECT r.RegionName, t.Name FROM Region r
JOIN (SELECT Name, RegionID FROM Customer WHERE RegionID = 5) AS t
ON t.RegionID = r.RegionID
All of these SQL snippets will return the same data. But with temp tables, we could, for example, create an index in the temp table to improve performance. There’s some good discussion here on the differences between temporary tables and subqueries.
WITH Employee_CTE (EmployeeNumber, Title)
AS
(SELECT NationalIDNumber,
JobTitle
FROM HumanResources.Employee)
SELECT EmployeeNumber,
Title
FROM Employee_CTE


SELECT c.Name,
c.City,
(SELECT CompanyName FROM Company WHERE ID = c.CompanyID) AS CompanyName
FROM Customer c
In particular, the problem is that the inner query (SELECT CompanyName…) is run for each row returned by the outer query (SELECT c.Name…). But why go over them Company again and again for every row processed by the outer query?
SELECT c.Name,
c.City,
co.CompanyName
FROM Customer c
LEFT JOIN Company co
ON c.CompanyID = co.CompanyID
In this case, we go over the Company the table just once, at the start, and JOIN it with the Customer table. From then on, we can select the values we need (co.CompanyName) more efficiently.
SELECT* FROM Customers WHERE address LIKE ‘%bar%’;
Here, the database will not be able to use a suitable index if it exists because of % wildcard. The system starts by performing a full table scan and this takes a toll on its speed.
SELECT* FROM Customers WHERE address LIKE ‘bar%’;
7. Avoid using COUNT(), instead of Exist()[ref]
EXISTS(). If you want to check if a record exists, use EXISTS(),
IF EXISTS(SELECT FIRSTNAME FROM EMPLOYEES WHERE FIRSTNAME LIKE '%JOHN%')
PRINT 'YES'
instead of COUNT(),
IF (SELECT COUNT(1) FROM EMPLOYEES WHERE FIRSTNAME LIKE '%JOHN%') > 0
PRINT 'YES'
While COUNT() scans the entire table, counting up all entries matching your condition, EXISTS() will exit as soon as it sees the result it needs.
SELECT
COUNT(*)
FROM
forest
WHERE
fruit_color = 5; /* 5 = red */
Assuming the column fruit_color's type is VARCHAR, or just anything non-numeric, indexing that column won't be very helpful, as the required implicit cast will prevent the database from using the index for the filtering process.
SELECT Customers.CustomerID, Customers.Name, Count(Sales.SalesID)
FROM Customers
INNER JOIN Sales
ON Customers.CustomerID = Sales.CustomerID
GROUP BY Customers.CustomerID, Customers.Name
HAVING Sales.LastSaleDate BETWEEN #1/1/2016# AND #12/31/2016#
This query would pull 1,000 sales records from the Sales table, then filter for the 200 records generated in the year 2016, and finally count the records in the dataset.
SELECT Customers.CustomerID, Customers.Name, Count(Sales.SalesID)
FROM Customers
INNER JOIN Sales
ON Customers.CustomerID = Sales.CustomerID
WHERE Sales.LastSaleDate BETWEEN #1/1/2016# AND #12/31/2016#
GROUP BY Customers.CustomerID, Customers.Name
This query would pull the 200 records from the year 2016, and then count the records in the dataset. The first step in the HAVING clause has been completely eliminated.
SELECT Customers.CustomerID, Customers.Name, Count(Sales.SalesID)
FROM Customers
INNER JOIN Sales
ON Customers.CustomerID = Sales.CustomerID
WHERE Sales.LastSaleDate BETWEEN #1/1/2016# AND #12/31/2016#
GROUP BY Customers.CustomerID, Customers.Name
HAVING Count(Sales.SalesID) > 5
11. Avoid OR conditions[ref]
SELECT
COUNT(*)
FROM
fb_posts
WHERE
username = ‘Mark’
OR post_time > ‘2018-01-01’
Having an index on both the username and post_time columns might sound helpful, but in most cases, the database won't use it, at least not in full. The reason will be the connection between the two conditions - the OR operator, which makes the database fetch the results of each part of the condition separately.
SELECT …
FROM …
WHERE username = ‘Mark’
UNION
SELECT …
FROM …
WHERE post_time > ‘2018-01-01’
Please note that if you don't mind duplicate records in your result set, you can also use UNION ALL (which will perform better than the default UNION DISTINCT).
SELECT
username, post_type
FROM
fb_posts
ORDER BY username ASC , post_type DESC
MySQL (and so many other relational databases), cannot use indexes when sorting with a mixed order (both ASC and DESC in the same ORDER BY clause). This changed with the release of the reversed indexes functionality and MySQL 8.x.
Summary
- Performance tuning select statements --- Google
- 7 SQL Query Performance Tuning Tips --- klipfolio.com
- Avoid using COUNT() --- 7
- Avoid using wildcard characters at the beginning of LIKE pattern --- 6
- Supercharge Your SQL Queries for Production Databases --- sisense.com
- Create joins with INNER JOIN (not WHERE --- Cross Join) --- 1
- Use WHERE instead of HAVING to define filters --- 10
- Use wildcards at the end of a phrase only --- 6
- SQL Tuning or SQL Optimization --- beginner-sql-tutorial.com
- Do not use HAVING clause for any other purposes. --- 10
- SQL Database Performance Tuning for Developers --- toptal
- Avoid Correlated SQL Subqueries --- 4
- Wise Use of Temporary Tables (#Temp) --- 3-a,b
- Use Exist instead of Count(*) --- 7
- SQL Performance Tuning: 5 Best Tips for Developers --- eversql.com
- Avoid OR conditions --- 11
- Avoid sorting with a mixed order --- 12
- Avoid conditions with different column types --- 9
- Query optimization techniques in SQL Server: tips and tricks --- sqlshack.com
- 25 tips to Improve SQL Query Performance --- winwire.com
- SQL Query Performance --- medium.com
- 4.Able Variables and Joins --- 3
- 7.Avoid Using GROUP BY, ORDER BY, and DISTINCT --- 8
- 9. Use the Same Datatype on JOIN and WHERE Clauses --- 9
- 11.Avoid Multiple Joins in a Single Query --- 2
- 12. Avoid Multi-Statement Table-Valued Functions (TVFs) --- 5
- Common Table Expressions (Introduction to CTE’s) --- 3-c, essentialsql.com
- SQL Server Performance Tuning Tips --- c-sharpcorner
- Avoid Null value in the fixed-length field
- Normalize tables in a database
- Keep Clustered Index Small
- Use Appropriate Datatype
- Store image path instead of the image itself
- USE Common Table Expressions (CTEs) instead of Temp table
- Use Appropriate Naming Convention
- Use UNION ALL instead of UNION
- Use Small data type for Index
- Use Stored Procedure
- Use Between instead of In
- Use If Exists to determine the record
- Avoid Cursors
- SET NOCOUNT ON
- Remove Unused Index
- Drop Index before Bulk Insertion of Data
- Avoid Loops In Coding
- Avoid Correlated Queries
- Avoid index and join hints
- Avoid Use of Temp table
- Use View for complex queries
- Make Transaction short
- Use Full-text Index

Join the conversation! Your thoughts help the community grow.