March 29, 2011 at 9:34 am
To give some more information, a functionally equivalent computed column that's calculated from a scalar function produces a seek as expected. Test case updated below to include both:
USE tempdb
GO
IF EXISTS (SELECT 1 FROM sys.objects WHERE name='Test_Computed_Index' AND type='U') <DROP> TABLE dbo.Test_Computed_Index
IF EXISTS (SELECT 1 FROM sys.objects WHERE name='fn_concat' AND type='FN') DROP FUNCTION fn_concat
GO
CREATE FUNCTION dbo.fn_concat (@system_ID int, @Reference int, @season varchar(3))
RETURNS Varchar(10) WITH SCHEMABINDING
AS
BEGIN
RETURN CAST(CASE WHEN @System_ID = 1
THEN CAST(@Reference AS VARCHAR(7))
+ '/'
+ @season
WHEN @System_ID = 2
THEN CAST(@Reference AS VARCHAR(10))
ELSE CAST(@Reference AS VARCHAR(10))
END AS VARCHAR(10))
END
GO
CREATE TABLE Test_Computed_Index
(
Test_ID INT PRIMARY KEY CLUSTERED
IDENTITY(1, 1) ,
Reference INT NOT NULL ,
Season VARCHAR(3) NOT NULL ,
System_ID INT ,
Computed_Concatenated_Field AS CAST(CASE WHEN System_ID = 1
THEN CAST(Reference AS VARCHAR(7))
+ '/' + Season
WHEN System_ID = 2
THEN CAST(Reference AS VARCHAR(10))
ELSE CAST(Reference AS VARCHAR(10))
END AS VARCHAR(10)) PERSISTED ,
Computed_Concatenated_Field_From_Function AS dbo.fn_concat(system_ID,
Reference,
Season)
)
CREATE INDEX IDX_Computed_Concatenated_Field ON dbo.Test_Computed_Index(Computed_Concatenated_Field)
CREATE INDEX IDX_Computed_Concatenated_Field_Function ON dbo.Test_Computed_Index(Computed_Concatenated_Field_From_Function)
GO
INSERT INTO dbo.Test_Computed_Index
( Reference, Season, System_ID )
SELECT 1, 'A01',1 UNION ALL
SELECT 2,'A02',2 UNION ALL
SELECT 3, 'A01',1 UNION ALL
SELECT 4,'A02',2 UNION ALL
SELECT 5, 'A01',1 UNION ALL
SELECT 6,'A02',2 UNION ALL
SELECT 7, 'A01',1 UNION ALL
SELECT 8,'A02',2 UNION ALL
SELECT 9, 'A01',1 UNION ALL
SELECT 10,'A02',2 UNION ALL
SELECT 11, 'A01',1 UNION ALL
SELECT 12,'A02',2 UNION ALL
SELECT 13, 'A01',1 UNION ALL
SELECT 14,'A02',2
--produces scan in 2008 SP1, seek in 2005 SP2
SELECT Test_ID FROM dbo.Test_Computed_Index
WHERE Computed_Concatenated_Field = '11/A01'
--produces seek in 2008 SP1 and 2005 SP2
SELECT Test_ID FROM dbo.Test_Computed_Index
WHERE Computed_Concatenated_Field_From_Function = '11/A01'
Does anyone have a SQL 2008 SP2 instance available they could test this on, so I know it's not already fixed (if it is a bug)?
P.S. Sorry for the brackets around the word <DROP> - please remove this when testing the code. Bizarrely, my corporate firewall detects it as a SQL Injection attempt and kills the connection if I remove the brackets 😀
March 29, 2011 at 9:47 am
Thanks Paul, yep that fixes it.
I'm aware PERSISTED isn't required if it's being indexed - it was one of the things I added in trying to troubleshoot what the problem might be 😛
Ok, so there's some sort of optimisation that evaluates adjacent case expressions simultaneously as they are the same calculation and this changes the definition behind the scenes? Makes sense - thanks for the explanation
March 29, 2011 at 10:35 am
Wouldn't it be also an option just to remove the redundant CASE expression?
Something like
CASE
WHEN System_ID = 1 THEN CAST(Reference AS VARCHAR(10)) + '/' + CAST(Season AS VARCHAR(10))
ELSE CAST(Reference AS VARCHAR(10))
END
I'm just wondering why there would be the need to add another expression leading to the same result as the ELSE part does... All I can think of is readability. Are there any other reasons?
March 29, 2011 at 10:40 am
HowardW (3/29/2011)
Does anyone have a SQL 2008 SP2 instance available they could test this on, so I know it's not already fixed (if it is a bug)?
I was using a 2008 SP2 earlier. Same behaviour.
Gail Shaw
Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability
March 29, 2011 at 11:23 am
HowardW (3/29/2011)
...so I know it's not already fixed (if it is a bug)?
It's a regression, but not a bug IMO. It seems there has been an enhancement that collapses duplicate expressions - so the behaviour seen here is just an unfortunate side-effect (the literal computed column definition can't be matched to the optimizer's metadata, so an index seek is not available). No doubt there are cases where this extra optimization produces better plans, so the regression is unfortunate but easily avoided.
Paul White
SQLPerformance.com
SQLkiwi blog
@SQL_Kiwi
March 29, 2011 at 11:32 am
LutzM (3/29/2011)
Wouldn't it be also an option just to remove the redundant CASE expression?Something like
CASE
WHEN System_ID = 1 THEN CAST(Reference AS VARCHAR(10)) + '/' + CAST(Season AS VARCHAR(10))
ELSE CAST(Reference AS VARCHAR(10))
END
I'm just wondering why there would be the need to add another expression leading to the same result as the ELSE part does... All I can think of is readability. Are there any other reasons?
Yes, it's definitely redundant in the test example. The real code was slightly different, there were more than three case conditions and it wasn't the second to last + else that were identical. The original case was something like this:
CASE WHEN <BOOLEAN EXPRESSION1> THEN 'Do this' WHEN <BOOLEAN EXPRESSION2>THEN 'Do This' .....
which I've now re-written as:
CASE WHEN <BOOLEAN EXPRESSION1> or <BOOLEAN EXPRESSION2> THEN 'Do This'
.....
Thanks everyone for the help - I've learned loads today
Viewing 6 posts - 16 through 20 (of 20 total)
You must be logged in to reply to this topic. Login to reply