Tell me more ×
Database Administrators Stack Exchange is a question and answer site for database professionals who wish to improve their database skills and learn from others in the community. It's 100% free, no registration required.

We have a query that we're running on SQL Server 2008 in the Management Studio. I can't give the exact query details, but let's say it's equivalent to this:

SELECT * 
FROM   MyView a (nolock) INNER JOIN MyTable b (nolock) 
           ON a.id = b.id AND a.thedate > b.thedate
WHERE  a.period_start < '1/1/2012' 
AND    a.invoice_status = 5 
AND    b.memo like '%something' 
AND    b.memo LIKE '%something else%'

This query runs relatively fast, returning 100 records in less than a second.

I then change the * in the select to explicitly list a few specific columns I want to see from each joined table. (Nothing fancy, just regular columns.) For example:

SELECT a.column1, a.column2, b.column1, b.column2 
FROM   MyView a (nolock) INNER JOIN MyTable b (nolock) 
           ON a.id = b.id AND a.thedate > b.thedate
WHERE  a.period_start < '1/1/2012' 
AND    a.invoice_status = 5 
AND    b.memo like '%something' 
AND    b.memo LIKE '%something else%'

Suddenly the query is taking many, many minutes to execute.

Lastly, I run the same query, but I add the * back in, to the last position of the select column list, and the query is back to the same fast speeds! What gives? I'm looking at the estimated execution plans, and I see the faster version mentions a "Hash Match" early on in the query that I don't see in the slower version.

SELECT a.column1, a.column2, b.column1, b.column2, * 
FROM   MyView a (nolock) INNER JOIN MyTable b (nolock) 
          ON a.id = b.id AND a.thedate > b.thedate
WHERE  a.period_start < '1/1/2012' 
AND    a.invoice_status = 5 
AND    b.memo like '%something' 
AND    b.memo LIKE '%something else%'

Is there a way to address this, or do we have to keep the asterisk in place?

share|improve this question
Do you have SET EXPLAIN or some other way to get the query plans of the two queries. What's the difference between them? – Jonathan Leffler May 2 '12 at 0:05
@Jonathan, I asked the same question, then deleted it when I saw the text just above the last query. :-) – Aaron Bertrand May 2 '12 at 0:17
@Mason, can you share more details, e.g. about the indexing on MyTable, how many rows from each side, how many rows returned by the query, what happens if you join against the necessary underlying table(s) instead of the view, the indexes on the table(s) referenced by the view, whether you really are okay with NOLOCK, and whether you have tried explicitly specifying a loop join (which I assume is the type of join used in the faster version of the query)? – Aaron Bertrand May 2 '12 at 0:20
When you use * you are using all the fields from all the tables in the view. When using a.c1, a.c2, you may only be using fields from a sub-set of the tables in the view. Are you able to see the contents of the view? And can you run a test selecting at least 1 field from every table referenced by the view? Finally, could you generate the actual execution plans for each, rather than the estimated plans? – MatBailie May 2 '12 at 0:21
@AaronBertrand: I think people are likely to need to see the query plans; or maybe the question on the DBA SE site contains the answer. It is highly DBMS-specific, whatever it is. I suspect, though, that the problem only reproduces on his specific table because of some peculiar circumstances, but he can't reveal more of the details for risk of ... well, exposing something. I think it could be mapped to anodyne Table1 with columns Column1, ..., but that requires some effort on his part to sanitize the query plan. – Jonathan Leffler May 2 '12 at 0:21
show 6 more comments

migrated from stackoverflow.com Jun 18 '12 at 3:29

2 Answers

You could try forcing the hash match, give this a go? :

SELECT a.column1, a.column2, b.column1, b.column2  
FROM   MyView a (nolock) 
INNER HASH JOIN MyTable b (nolock) ON a.id = b.id AND a.thedate > b.thedate 
WHERE  a.period_start < '1/1/2012'  
AND    a.invoice_status = 5  
AND    b.memo like '%something'  
AND    b.memo LIKE '%something else%' 
share|improve this answer
This is definitely getting faster results than my middle query, but not quite as fast as leaving the joins as-is and specifying the * at the end of the select list. Thanks for the reply! – Mason G. Zhwiti May 2 '12 at 16:38

As to the question "Why" the answer will be that selecting all the columns prompts SQL Server to use a different query plan.

For example, if you select columns which do not have an index, SQL Server knows it is eventually going to have to read the table pages, so might conclude that doing a table scan would be faster, or might conclude that using a different index, then using a clustered index seek would be faster.

However if you only select columns which have an index, SQL Server will probably think it is faster scan the index and read the data from there.

Now, what with the query optimiser being too clever by half, it often gets this wrong. In your case I would guess that the table scan and/or clustered index seek with the hash join is faster but SQL thinks it is slower, and only does it because you are asking for all the columns.

The solution is either to force the plan, or possibly better, to create indexes on the date fields.

Also, you can sometimes improve the plan by forcing short-circuit order of the where clause. In your case:

/* Example how to turn an AND AND AND into a CASE. Note the NOT. */
case
when NOT a.period_start < '1/1/2012'   then 0
when NOT a.invoice_status = 5   then 0
when NOT b.memo like '%something'   then 0
when NOT b.memo LIKE '%something else%' then 0
else 1 end = 1

That would have the same logical effect as your where clause, but prevents SQL from using the period_start or invoice_status indexes, and also forces those to be evaluated before the memo condition.

/* Example how to turn an OR OR OR into a CASE */
case
when a.period_start < '1/1/2012'   then 1
when a.invoice_status = 5   then 1
when b.memo like '%something'   then 1
when b.memo LIKE '%something else%' then 1
else 0 end = 1
share|improve this answer
The short circuit example may save CPU cycles per row, but it does obfuscate all of the fields when determining use of indexes. In particular, the AND version of the CASE block would be preventing any possible INDEX SEEKs. – MatBailie May 2 '12 at 11:36
Yes, you would add items to the Case only when that was a desired effect, for example an inappropriate index was being chosen resulting in a bad plan. – Ben May 2 '12 at 11:39
Thanks for the reply, this is a very interesting technique. My question would be, is it better/faster to use this approach, or just add * to the select list? I guess it depends on what is the root cause of my issue. – Mason G. Zhwiti May 2 '12 at 16:39
@MasonG.Zhwiti, I wouldn't do this just on the off-chance it might help. Only do it really when you can prove it helps with before/after stats. Really you need to get a full query plan and understand why it is doing what it is doing in each case. Then you can force the plan using index hints, or join hints, etc. – Ben May 2 '12 at 17:02

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.