Considérez ces deux fonctions:
ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)
ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)
Autant que je sache, ils produisent exactement le même résultat. En d'autres termes, l'ordre dans lequel vous répertoriez les colonnes de la PARTITION BY
clause n'a pas d'importance.
S'il y a un index, (A,B,C)
je m'attendais à ce que l'optimiseur utilise cet index dans les deux variantes.
Mais, étonnamment, l'optimiseur a décidé de faire un tri extra explicite dans la deuxième variante.
Je l'ai vu sur SQL Server 2008 Standard et SQL Server 2014 Express.
Voici un script complet que j'ai utilisé pour le reproduire.
Testé sur Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20 février 2014 20:04:26 Copyright (c) Microsoft Corporation Express Edition (64 bits) sur Windows NT 6.1 (Build 7601: Service Pack 1)
et Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27 mai 2016 15:33:17 Copyright (c) Microsoft Corporation Express Edition (64 bits) sur Windows NT 6.1 (Build 7601: Service Pack 1)
avec l'ancien et le nouveau Cardinality Estimator en utilisant OPTION (QUERYTRACEON 9481)
et OPTION (QUERYTRACEON 2312)
.
Configurer la table, l'index et les exemples de données
CREATE TABLE [dbo].[T](
[ID] [int] IDENTITY(1,1) NOT NULL,
[A] [int] NOT NULL,
[B] [int] NOT NULL,
[C] [int] NOT NULL,
CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_ABC] ON [dbo].[T]
(
[A] ASC,
[B] ASC,
[C] ASC
)WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON)
GO
INSERT INTO [dbo].[T] ([A],[B],[C]) VALUES
(10, 20, 30),
(10, 21, 31),
(10, 21, 32),
(10, 21, 33),
(11, 20, 34),
(11, 21, 35),
(11, 21, 36),
(12, 20, 37),
(12, 21, 38),
(13, 21, 39);
Requêtes
SELECT -- AB
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);
SELECT -- BA
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);
SELECT -- both
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);
Plans d'exécution
PARTITION PAR A, B
PARTITION PAR B, A
Tous les deux
Comme vous pouvez le voir, le deuxième plan a un tri supplémentaire. Il ordonne par B, A, C. L'optimiseur, apparemment, n'est pas assez intelligent pour réaliser que PARTITION BY B,A
c'est la même chose PARTITION BY A,B
et pour trier à nouveau les données.
Fait intéressant, la troisième requête contient les deux variantes ROW_NUMBER
et il n'y a pas de tri supplémentaire! Le plan est le même que pour la première requête. (Le projet de séquence a une expression supplémentaire dans la liste de sortie pour la colonne supplémentaire, mais pas de tri supplémentaire). Donc, dans ce cas plus compliqué, l'optimiseur semblait être assez intelligent pour réaliser que PARTITION BY B,A
c'est la même chose que PARTITION BY A,B
.
Dans les première et troisième requêtes, l'opérateur Index Scan a la propriété Ordered: True, dans la deuxième requête, il est False.
Encore plus intéressant, si je réécris la troisième requête comme ceci (permutez deux colonnes):
SELECT -- both
ID,A,B,C
,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);
alors le tri supplémentaire apparaît à nouveau!
Quelqu'un pourrait-il faire la lumière? Que se passe-t-il ici dans l'optimiseur?