SQL Server (2014) comportement impair à partir de variations sur IN (list)

J'ai essayé d'optimiser (ou au less de changer) du code EF en C # pour utiliser des procédures stockées, et j'ai trouvé ce qui me semble être une anomalie (ou quelque chose de nouveau) quand je trouve des lignes correspondant à une list constante. La requête courte typique générée manuellement serait quelque chose comme …

SELECT Something FROM Table WHERE ID IN (one, two, others); 

Nous avions une requête EF que nous remplacions par un appel de procédure stockée, donc j'ai regardé la sortie, j'ai vu que c'était complexe et j'ai pensé que ma requête plus simple (similaire à la précédente) serait meilleure. Ce n'était pas. Voici une démo rapide qui reproduit ceci.

Quelqu'un peut-il expliquer pourquoi les plans d'exécution pour la version finale – avec le

 ...WHERE EXISTS(... (SELECT 1 AS X) AS Alias UNION ALL...) AS Alias...) 

La construction est meilleure – apparemment parce qu'elle omet l'opération SORT coûteuse, même si le plan inclut DEUX balayages d'index plutôt que celui de la requête plus simple.

Voici un exemple de script autonome (j'espère) …

 USE SandBox; -- a dummy database, not a live one! -- create our dummy table, dropping first if it exists IF EXISTS (SELECT NULL FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Test') DROP TABLE Test; CREATE TABLE Test (Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, FormId INT NOT NULL, DateRead DATE NULL); -- populate with some data INSERT INTO Test VALUES (1, NULL), (1, GETDATE()), (1, NULL), (4, NULL), (5, NULL), (6, GETDATE()); -- Simple query that I might typically use -- how many un-read ensortinges are there for a set of 'forms' of interest, 1, 5 and 6 -- (we're happy to omit forms with none) SELECT T.FormId, COUNT(*) AS TheCount FROM Test AS T WHERE T.FormId IN (1, 5, 6) AND T.DateRead IS NULL GROUP BY T.FormId; -- This is the first step towards the EF-generated code -- using an EXISTS gives basically the same plan but with constants SELECT T.FormId, COUNT(*) AS TheCount FROM Test T WHERE EXISTS ( SELECT NULL FROM (VALUES (1), (5), (6) ) AS X(FormId) WHERE X.FormId = T.FormId ) AND T.DateRead IS NULL GROUP BY T.FormId; -- A step closer, using UNION ALL instead of VALUES to generate the 'table' -- still the same plan SELECT T.FormId, COUNT(*) AS TheCount FROM Test T WHERE EXISTS ( SELECT NULL FROM ( SELECT 1 UNION ALL SELECT 5 UNION ALL SELECT 6 ) AS X(FormId) WHERE X.FormId = T.FormId ) AND T.DateRead IS NULL GROUP BY T.FormId; -- Now what the EF actually generated (cleaned up a bit) -- Adding in the "FROM (SELECT 1 as X) AS alias" changes the execution plan considerably and apparently costs less to run SELECT T.FormId, COUNT(*) AS TheCount FROM Test T WHERE EXISTS ( SELECT NULL FROM ( SELECT 1 FROM (SELECT 1 AS X) AS X1 UNION ALL SELECT 5 FROM (SELECT 1 AS X) AS X2 UNION ALL SELECT 6 FROM (SELECT 1 AS X) AS X3 ) AS X(FormId) WHERE X.FormId = T.FormId ) AND T.DateRead IS NULL GROUP BY T.FormId; 

Quelqu'un peut-il m'aider à comprendre pourquoi et s'il y a un avantage dans une utilisation plus large pour ce type de format de requête?

J'ai regardé autour de quelque chose de spécial dans (SELECT 1 AS X) choses et bien que beaucoup le montrent comme étant commun dans la sortie EF, je ne pouvais rien voir sur cet avantage apparent apparent.

Merci d'avance,

Keith

Les prédicats derrière chacune des parsings de l'index dans la dernière de ces requêtes sont aa plage> = 1 et id <= 6 et DateRead IS NULL

compris entre 1 et 6

J'ai ajouté un autre "select 1" et cela a créé un autre index index. Il semble que chaque (select 1) est littéralement traité comme une table à part entière malgré les ALL UNION pour le former en une seule table.

Alors que dans toutes les requêtes précédentes, les prédicats pour l'parsing d'index sont un set de DateRead IS NULL suivi de ORs

1 ou 5 ou 6

Je pensais que j'appendais ceci dans le mélange:

 declare @tmp table (formid int not null primary key) insert into @tmp values (1),(5),(6); SELECT T.FormId, COUNT(*) AS TheCount FROM Test T WHERE EXISTS ( SELECT NULL FROM @tmp X WHERE X.FormId = T.FormId ) AND T.DateRead IS NULL GROUP BY T.FormId; 

Mais cela comprenait aussi une sorte.

dbfiddle.uk permet d'accéder à l'intégralité du showplan xml (un peu fastidieusement) donc si vous êtes intéressé: un dbfiddle est ici