I'm getting behavior in a query plan I cannot explain. The difference is between two filtered indexes I'm testing with. One uses a where id is not null
and the other uses where id > 0
. In my actual data I get a 95% favorable runtime using the > 0
index. I can't see why they would be different... my keys are auto incrementing integers starting at 1 referenced by the adjoining table on a nullable column. Below is a script that will generate structures and data analogous to my production data.
Please note the following... if you execute these scripts and then compare the two select statements, you will probably get fifty fifty performance like I did. However in my production data the query that utilizes the > 0
filtered index chooses an index scan instead of seek. This scan runs much faster. Below is a screenshot of my actual query plan comparison. Secondly, I have rebuilt these indexes, fragmentation is not an issue.
Questions:
Why the disparity? Where does the scan vs seek come from? Wouldn't is not null
and > 0
be equivalent in a join when the datatype is int identity(1,1)?
Schema + Data:
if exists (select * from sys.tables where name = 'sometable')
begin drop table sometable end
go
create table sometable (id int not null identity(1,1) primary key
, value nvarchar(50));
go
insert into sometable values ('a test value');
insert into sometable values ('a test value');
insert into sometable values ('a test value');
insert into sometable values ('a test value');
insert into sometable values ('a test value');
go
if exists (select * from sys.tables where name = 'audit')
begin drop table audit end
go
create table audit (id int not null identity(1,1) primary key
, sometable_id int null
, someothertable_id int null
, auditvalue nvarchar(50));
go
declare @count int = 0;
while (@count < 40000)
begin
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'a sometable audit');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'another sometable audit');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,1,'irrelevant other table audit');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'another audit for record one sometable');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'record three audit');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'another record 3 audit');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,50,'irrelevant1');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,51,'irrelevant2');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,52,'irrelevant3');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'relevant fourth record');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'back to one record');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,53,'irrelevant 4');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,54,'irrelevant fifth record');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,55,'irrelevant sixth record');
insert into audit (sometable_id,someothertable_id,auditvalue) values (null,56,'irrelevant seventh record');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'a fifth record audit');
insert into audit (sometable_id,someothertable_id,auditvalue) values (floor(rand()*5+1),null,'another fourth record audit');
set @count = (@count + 1);
end
go
--drop index audit.filter_null_audits_for_sometable
create index filter_null_audits_for_sometable on audit(sometable_id,id) include(auditvalue) where sometable_id is not null;
go
--drop index audit.filter_upzero_audits_for_sometable
create index filter_upzero_audits_for_sometable on audit(sometable_id,id) include(auditvalue) where sometable_id > 0;
go
Two Queries:
select top 50000 a.id sometableid,a.value,b.id auditid,b.auditvalue
from sometable a
join audit b with(index(filter_null_audits_for_sometable)) on a.id = b.sometable_id
select top 50000 a.id sometableid,a.value,b.id auditid,b.auditvalue
from sometable a
join audit b with(index(filter_upzero_audits_for_sometable)) on a.id = b.sometable_id and b.sometable_id > 0
Update1
I copied production data into my test tables. Instead of fifty fifty, the results matched the included query plan and reproduced the disparity. The test scenario is structurally analogous.
Update2
select a.id sometableid,a.value,b.id auditid,b.auditvalue
from sometable a
inner merge join audit b with(index(filter_null_audits_for_sometable))
on a.id = b.sometable_id
select a.id sometableid,a.value,b.id auditid,b.auditvalue
from sometable a
join audit b with(index(filter_upzero_audits_for_sometable))
on a.id = b.sometable_id
These query plans will not compile. Why? They force me to use > 0
as query join conditions to get the optimized plan.