An Azure relational database service.
There are several challenges here. The first is: How do you ensure that any rewrite of the query does not change the result? What I do in a situation like this is that I save the result of the query into a table. After having rewritten the query, I run the query again and save to a new table. Then I can run some queries to compare. The most simple-minded are:
SELECT * FROM #oldVersion
EXCEPT
SELECT * FROM #newVersion
SELECT * FROM #newVersion
EXCEPT
SELECT * FROM #oldVersion
If both result sets are empty, great! This means that the two different versions produce the same result. If they produce different results, this calls for further investigations. The differences between acceptable, for instance because there some parts that are indeterministic so that anything goes. But it also happened to me - more than once! - that I've introduced differences in the business logic, and the new version has proven to be the correct one. For these investigations FULL JOIN queries can be useful.
As for the query tuning, we cannot say anything about the query as such, as we don't see anything. But if you share the query text and the query plan (in XML format), we might be able to come up with some ideas.
What we can say from a general perspective is this:
- First investigate: Does the rewritten query with the two extra tables actually produce the correct results? It has happened to me more than once when a query has been slow, there was a problem in the logic. A silly error like
ON a.col = a.col, or an incomplete join condition. It's useless trying to tune an incorrect query. - Review the query text itself: Are indexed columns entangled in expressions? Are there implicit conversions? If there are, can these be avoided? These are often blockers for using indexes, and thereby trouble makers for performance.
- Even if there are plenty of indexes, indexing can still be improved. Maybe some indexes should have some included columns added, to prevent key lookups and dissuade the optimizer from scanning to avoid those key lookups.
- Inspect the plan for misestimates. Below each operator, you see things like 546 of 685. That is, the actual number of rows was 546 and the estimate was 685. Such a difference is perfectly OK. But if you use you see 212098 of 1 or the reverse, this worth investigating why this is happening.
- Even if the query does not include any blockers I mentioned in #2 and all possible indexes are present, a 10-join query can easily go wrong, as it is a tough game for the optimizer to get it right. A small estimation error can easily to explode. For this reason, it can often help to break up the query in two or more where you store intermediate results in tempt tables. Since the temp table(s) have statistics, the optimizer gets more accurate data when working with the next steps. Of course, then you need to find a good way to break up the query. But here you are in a better situation than the optimizer: Presumably, you know more about the data, than the optimizer that only knows the index and statistics.