Considere estas dos funciones:
ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)
ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)
Por lo que yo entiendo, producen exactamente el mismo resultado. En otras palabras, el orden en el que enumera las columnas en la PARTITION BY
cláusula no importa.
Si hay un índice en (A,B,C)
espera que el optimizador use este índice en ambas variantes.
Pero, sorprendentemente, el optimizador decidió hacer una ordenación adicional explícita en la segunda variante.
Lo he visto en SQL Server 2008 Standard y SQL Server 2014 Express.
Aquí hay un script completo que usé para reproducirlo.
Probado en Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20 de febrero de 2014 20:04:26 Copyright (c) Microsoft Corporation Express Edition (64 bits) en Windows NT 6.1 (Build 7601: Service Pack 1)
y Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27 de mayo de 2016 15:33:17 Copyright (c) Microsoft Corporation Express Edition (64 bits) en Windows NT 6.1 (Build 7601: Servicio Paquete 1)
con el estimador de cardinalidad antiguo y el nuevo utilizando OPTION (QUERYTRACEON 9481)
y OPTION (QUERYTRACEON 2312)
.
Configurar tabla, índice, datos de muestra
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);
Consultas
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);
Planes de ejecucion
PARTICIÓN POR A, B
PARTICIÓN POR B, A
Ambos
Como puede ver, el segundo plan tiene una clasificación adicional. Ordena por B, A, C. El optimizador, aparentemente, no es lo suficientemente inteligente como para darse cuenta de que PARTITION BY B,A
es lo mismo PARTITION BY A,B
y vuelve a ordenar los datos.
Curiosamente, la tercera consulta tiene ambas variantes ROW_NUMBER
, ¡y no hay una clasificación adicional! El plan es el mismo que para la primera consulta. (El Proyecto de secuencia tiene una expresión adicional en la Lista de salida para la columna adicional, pero no tiene una Clasificación adicional). Entonces, en este caso más complicado, el optimizador parecía ser lo suficientemente inteligente como para darse cuenta de que PARTITION BY B,A
es lo mismo PARTITION BY A,B
.
En la primera y tercera consulta, el operador de exploración de índice tiene la propiedad Ordenado: Verdadero, en la segunda consulta es Falso.
Aún más interesante, si reescribo la tercera consulta de esta manera (intercambia dos columnas):
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);
¡entonces el Sort extra aparece de nuevo!
¿Alguien podría arrojar algo de luz? ¿Qué está pasando en el optimizador aquí?