Pourquoi cet agrégat de flux est-il nécessaire?


12

Consultez cette requête. C'est assez simple (voir la fin de l'article pour les définitions de table et d'index, et un script de repro):

SELECT MAX(Revision)
FROM dbo.TheOneders
WHERE Id = 1 AND 1 = (SELECT 1);

Remarque: le "AND 1 = (SELECT 1) est juste pour empêcher cette requête d'être auto-paramétrée, ce qui, je le pensais, prêtait à confusion - il obtient en fait le même plan avec ou sans cette clause

Et voici le plan ( collez le lien du plan) :

planifier avec un flux agg

Puisqu'il y a un "top 1", j'ai été surpris de voir l'opérateur d'agrégat de flux. Cela ne me semble pas nécessaire, car il ne peut y avoir qu'une seule ligne.

Pour tester cette théorie, j'ai essayé cette requête logiquement équivalente:

SELECT MAX(Revision)
FROM dbo.TheOneders
WHERE Id = 1
GROUP BY Id;

Voici le plan pour celui-ci ( collez le lien du plan ):

planifier sans flux agg

Effectivement, le plan groupé peut s'en tirer sans l'opérateur d'agrégat de flux.

Notez que les deux requêtes lisent "en arrière" à partir de la fin de l'index et font un "top 1" pour obtenir la révision maximale.

Qu'est-ce que j'oublie ici? L'agrégat de flux fonctionne-t-il réellement dans la première requête, ou devrait-il être éliminé (et c'est juste une limitation de l'optimiseur que ce n'est pas le cas)?

Soit dit en passant, je me rends compte que ce n'est pas un problème incroyablement pratique (les deux requêtes rapportent 0 ms de CPU et le temps écoulé), je suis juste curieux de savoir les internes / comportements présentés ici.


Voici le code d'installation que j'ai exécuté avant d'exécuter les deux requêtes ci-dessus:

DROP TABLE IF EXISTS dbo.TheOneders;
GO

CREATE TABLE dbo.TheOneders
(
    Id INT NOT NULL,
    Revision SMALLINT NOT NULL,
    Something NVARCHAR(23),

    CONSTRAINT PK_TheOneders PRIMARY KEY NONCLUSTERED (Id, Revision)
);
GO

INSERT INTO dbo.TheOneders
    (Id, Revision, Something)
SELECT DISTINCT TOP 1000 
    1, m.message_id, 'Do...'
FROM sys.messages m
ORDER BY m.message_id
OPTION (MAXDOP 1);

INSERT INTO dbo.TheOneders
    (Id, Revision, Something)
SELECT DISTINCT TOP 100 
    2, m.message_id, 'Do that thing you do...'
FROM sys.messages m
ORDER BY m.message_id
OPTION (MAXDOP 1);
GO

Réponses:


16

Vous pouvez voir le rôle de cet agrégat si aucune ligne ne correspond à la WHEREclause.

SELECT MAX(Revision)
FROM   dbo.TheOneders
WHERE  Id = 1
       AND 1 = 1 /*To avoid auto parameterisation*/
       AND Id%3 = 4  /*always false*/

Dans ce cas, zéro ligne va dans l'agrégat mais il en émet toujours une car la sémantique correcte doit retourner NULLdans ce cas.

entrez la description de l'image ici

Il s'agit d'un agrégat scalaire par opposition à un agrégat vectoriel.

Votre requête "logiquement équivalente" n'est pas équivalente. L'ajout en GROUP BY Idferait un agrégat vectoriel, puis le comportement correct serait de ne renvoyer aucune ligne.

Voir Fun with Scalar and Vector Aggregates pour plus d'informations à ce sujet.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.