Rozważ te dwie funkcje:
ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)
ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)
O ile rozumiem, dają one dokładnie taki sam wynik. Innymi słowy kolejność wyświetlania kolumn w PARTITION BY
klauzuli nie ma znaczenia.
Jeśli istnieje indeks (A,B,C)
, oczekiwałem, że optymalizator użyje tego indeksu w obu wariantach.
Ale, co zaskakujące, optymalizator postanowił wykonać wyjątkowo wyraźne sortowanie w drugim wariancie.
Widziałem to na SQL Server 2008 Standard i SQL Server 2014 Express.
Oto pełny skrypt, którego użyłem do jego odtworzenia.
Wypróbowano na Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20 lutego 2014 20:04:26 Copyright (c) Microsoft Corporation Express Edition (64-bit) na Windows NT 6.1 (kompilacja 7601: Service Pack 1)
i Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27 maja 2016 15:33:17 Prawa autorskie (c) Microsoft Corporation Express Edition (64-bit) w systemie Windows NT 6.1 (kompilacja 7601: usługa Paczka 1)
zarówno ze starym, jak i nowym estymatorem liczności przy użyciu OPTION (QUERYTRACEON 9481)
i OPTION (QUERYTRACEON 2312)
.
Skonfiguruj tabelę, indeks, przykładowe dane
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);
Zapytania
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);
Plany wykonania
PODZIAŁ NA A, B
PODZIAŁ PRZEZ B, A
Obie
Jak widać, drugi plan ma dodatkowe sortowanie. Zamawia B, A, C. Optymalizator najwyraźniej nie jest wystarczająco inteligentny, aby zdać sobie sprawę, że PARTITION BY B,A
jest to samo PARTITION BY A,B
i ponownie sortuje dane.
Co ciekawe, trzecie zapytanie zawiera oba warianty ROW_NUMBER
i nie ma dodatkowego sortowania! Plan jest taki sam jak dla pierwszego zapytania. (Projekt sekwencji ma dodatkowe wyrażenie na liście wyników dla dodatkowej kolumny, ale bez dodatkowego sortowania). Tak więc w tym bardziej skomplikowanym przypadku optymalizator wydawał się być wystarczająco inteligentny, aby zdać sobie sprawę, że PARTITION BY B,A
to samo PARTITION BY A,B
.
W pierwszym i trzecim zapytaniu operator skanowania indeksu ma właściwość Uporządkowany: Prawda, w drugim zapytaniu jest False.
Jeszcze bardziej interesujące, jeśli ponownie napiszę trzecie zapytanie w ten sposób (zamień dwie kolumny):
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);
wtedy dodatkowe Sortowanie pojawi się ponownie!
Czy ktoś mógłby rzucić trochę światła? Co się tutaj dzieje w optymalizatorze?