Database
 sql >> база данни >  >> RDS >> Database

Номера на редове с недетерминиран ред

Прозоречната функция ROW_NUMBER има множество практически приложения, далеч отвъд очевидните нужди за класиране. През повечето време, когато изчислявате номера на редове, трябва да ги изчислите въз основа на някакъв ред и вие предоставяте желаната спецификация за подреждане в клаузата за реда на прозореца на функцията. Въпреки това, има случаи, в които трябва да изчислите номерата на редовете без определен ред; с други думи, базиран на недетерминистичен ред. Това може да бъде в целия резултат от заявката или в дялове. Примерите включват присвояване на уникални стойности на редове с резултати, премахване на дублирани данни и връщане на всеки ред за група.

Имайте предвид, че необходимостта от присвояване на номера на редове въз основа на недетерминиран ред е различно от необходимостта да ги присвоите въз основа на случаен ред. При първото просто не ви пука в какъв ред са присвоени и дали повтарящите се изпълнения на заявката продължават да присвояват едни и същи номера на редовете на същите редове или не. При последното очаквате повтарящи се изпълнения да продължат да променят кои редове се присвояват с кои номера на редовете. Тази статия изследва различни техники за изчисляване на номера на редове с недетерминиран ред. Надеждата е да се намери техника, която е едновременно надеждна и оптимална.

Специални благодарности на Пол Уайт за съвета относно постоянното сгъване, за техниката на константата по време на изпълнение и за това, че винаги сте чудесен източник на информация!

Когато поръчката има значение

Ще започна със случаите, когато подреждането на номера на редове има значение.

Ще използвам таблица, наречена T1 в моите примери. Използвайте следния код, за да създадете тази таблица и да я попълните с примерни данни:

SET NOCOUNT ON;
 
USE tempdb;
 
DROP TABLE IF EXISTS dbo.T1;
GO
 
CREATE TABLE dbo.T1
(
  id INT NOT NULL CONSTRAINT PK_T1 PRIMARY KEY,
  grp VARCHAR(10) NOT NULL,
  datacol INT NOT NULL
);
 
INSERT INTO dbo.T1(id, grp, datacol) VALUES
  (11, 'A', 50),
  ( 3, 'B', 20),
  ( 5, 'A', 40),
  ( 7, 'B', 10),
  ( 2, 'A', 50);

Помислете за следната заявка (ще я наречем Заявка 1):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Тук искате номерата на редове да бъдат присвоени във всяка група, идентифицирана от колоната grp, подредена от колоната datacol. Когато изпълних тази заявка в моята система, получих следния изход:

id  grp  datacol  n
--- ---- -------- ---
5   A    40       1
2   A    50       2
11  A    50       3
7   B    10       1
3   B    20       2

Номерата на редовете се задават тук в частично детерминиран и частично недетерминиран ред. Това, което имам предвид с това, е, че имате уверение, че в рамките на същия дял ред с по-голяма стойност на datacol ще получи по-голяма стойност на номера на реда. Въпреки това, тъй като datacol не е уникален в рамките на дяла grp, редът на присвояване на номера на редове между редове със същите стойности на grp и datacol е недетерминиран. Такъв е случаят с редовете със стойности на id 2 и 11. И двата имат grp стойност A и datacol стойност 50. Когато изпълних тази заявка в моята система за първи път, редът с id 2 получи ред номер 2 и ред с идентификатор 11 получи ред номер 3. Няма значение вероятността това да се случи на практика в SQL Server; ако изпълня заявката отново, теоретично, редът с идентификатор 2 може да бъде присвоен с ред номер 3, а редът с идентификатор 11 може да бъде присвоен с ред номер 2.

Ако трябва да присвоите номера на редове въз основа на напълно детерминиран ред, гарантирайки повторяеми резултати при изпълнението на заявката, стига основните данни да не се променят, трябва комбинацията от елементи в клаузите за разделяне на прозорци и подреждане да бъде уникална. В нашия случай това може да се постигне чрез добавяне на идентификатора на колона към клаузата за поръчка на прозореца като тайбрейка. Тогава клаузата OVER ще бъде:

OVER (PARTITION BY grp ORDER BY datacol, id)

Във всеки случай, когато се изчисляват номера на редове въз основа на някаква значима спецификация за подреждане, като в заявка 1, SQL Server трябва да обработи редовете, подредени чрез комбинацията от разделяне на прозорци и елементи за подреждане. Това може да се постигне или чрез изтегляне на предварително подредените данни от индекс, или чрез сортиране на данните. В момента няма индекс на T1, който да поддържа изчислението ROW_NUMBER в заявка 1, така че SQL Server трябва да избере сортиране на данните. Това може да се види в плана за заявка 1, показан на фигура 1.

Фигура 1:План за заявка 1 без поддържащ индекс

Забележете, че планът сканира данните от клъстерирания индекс със свойство Ordered:False. Това означава, че сканирането не трябва да връща редовете, подредени от индексния ключ. Това е така, тъй като клъстерираният индекс се използва тук само защото се случва да покрива заявката, а не поради неговия ключов ред. След това планът прилага сортиране, което води до допълнителни разходи, N Log N мащабиране и забавено време за реакция. Операторът Segment създава флаг, показващ дали редът е първият в дяла или не. И накрая, операторът Sequence Project присвоява номера на редове, започващи с 1 във всеки дял.

Ако искате да избегнете необходимостта от сортиране, можете да подготвите покриващ индекс с списък с ключове, който се основава на елементите за разделяне и подреждане, и списък за включване, който се основава на покриващите елементи. Харесва ми да мисля за този индекс като POC индекс (за разделяне , поръчване и покриване ). Ето дефиницията на POC, която поддържа нашата заявка:

CREATE INDEX idx_grp_data_i_id ON dbo.T1(grp, datacol) INCLUDE(id);

Стартирайте отново заявка 1:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Планът за това изпълнение е показан на Фигура 2.

Фигура 2:План за заявка 1 с POC индекс

Обърнете внимание, че този път планът сканира POC индекса със свойство Ordered:True. Това означава, че сканирането гарантира, че редовете ще бъдат върнати в реда на индексни ключове. Тъй като данните се изтеглят предварително подредени от индекса, както се нуждае от функцията на прозореца, няма нужда от изрично сортиране. Мащабирането на този план е линейно и времето за реакция е добро.

Когато поръчката няма значение

Нещата стават малко трудни, когато трябва да зададете номера на редове с напълно недетерминиран ред. Естественото нещо, което трябва да направите в такъв случай, е да използвате функцията ROW_NUMBER, без да посочвате клауза за ред на прозореца. Първо, нека проверим дали стандартът SQL позволява това. Ето съответната част от стандарта, дефинираща синтаксичните правила за функциите на прозореца:

Правила за синтаксис

5) Нека WNS бъде <име на прозореца или спецификация>. Нека WDX бъде дескриптор на структурата на прозореца, който описва прозореца, дефиниран от WNS.

6) Ако е указано , , или ROW_NUMBER, тогава:

a) Ако , , RANK или DENSE_RANK е указано, тогава трябва да присъства клаузата за подреждане на прозорци WOC на WDX.

f) ROW_NUMBER() OVER WNS е еквивалентен на <функция на прозореца>:COUNT (*) НАД (WNS1 РЕДОВЕ НЕОГРАНИЧЕНИ ПРЕДИШНИ)

Забележете, че точка 6 изброява функциите , , или ROW_NUMBER, а след това точка 6a казва, че за функциите , , RANK или DENSE_RANK трябва да присъства клаузата за ред на прозореца. Няма изричен език, посочващ дали ROW_NUMBER изисква клауза за подреждане на прозореца или не, но споменаването на функцията в точка 6 и пропускането й в 6a може да означава, че клаузата е незадължителна за тази функция. Доста очевидно е защо функции като RANK и DENSE_RANK изискват клауза за подреждане на прозореца, тъй като тези функции са специализирани в обработката на връзки, а връзките съществуват само когато има спецификация за подреждане. Със сигурност обаче можете да видите как функцията ROW_NUMBER може да се възползва от незадължителна клауза за поръчка на прозорец.

Така че, нека да опитаме и да се опитаме да изчислим номера на редове без подреждане на прозорци в SQL Server:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER() AS n 
FROM dbo.T1;

Този опит води до следната грешка:

Съобщение 4112, ниво 15, състояние 1, ред 53
Функцията 'ROW_NUMBER' трябва да има клауза OVER с ORDER BY.

Всъщност, ако проверите документацията на SQL Server за функцията ROW_NUMBER, ще намерите следния текст:

„клауза_по_порядък

Клаузата ORDER BY определя последователността, в която на редовете се присвоява уникален ROW_NUMBER в рамките на определен дял. Задължително е.”

Така че очевидно клаузата за ред на прозореца е задължителна за функцията ROW_NUMBER в SQL Server. Между другото, така е и в Oracle.

Трябва да кажа, че не съм сигурен, че разбирам мотивите зад това изискване. Не забравяйте, че позволявате да дефинирате номера на редове въз основа на частично недетерминиран ред, като в Заявка 1. Така че защо да не разрешите недетерминизма докрай? Може би има някаква причина, за която не мисля. Ако се сетите за такава причина, моля, споделете.

Във всеки случай можете да твърдите, че ако не ви пука за реда, като се има предвид, че клаузата за поръчка на прозореца е задължителна, можете да посочите всяка поръчка. Проблемът с този подход е, че ако поръчате по някаква колона от запитаната(ите) таблица(и), това може да доведе до ненужно намаляване на производителността. Когато няма поддържащ индекс, ще плащате за изрично сортиране. Когато има поддържащ индекс, вие ограничавате механизма за съхранение до стратегия за сканиране на реда на индекс (следвайки списъка с връзки към индекса). Не му позволявате повече гъвкавост, каквато обикновено има, когато редът няма значение при избора между сканиране на поръчка на индекс и сканиране на поръчка за разпределение (въз основа на IAM страници).

Една идея, която си струва да опитате, е да посочите константа, като 1, в клаузата за реда на прозореца. Ако се поддържа, бихте се надявали, че оптимизаторът е достатъчно интелигентен, за да осъзнае, че всички редове имат една и съща стойност, така че няма реална релевантност на подреждането и следователно няма нужда от принудително сортиране или сканиране на реда на индекс. Ето заявка, опитваща този подход:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1) AS n 
FROM dbo.T1;

За съжаление SQL Server не поддържа това решение. Той генерира следната грешка:

Съобщение 5308, ниво 16, състояние 1, ред 56
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат целочислени индекси като изрази на клауза ORDER BY.

Очевидно SQL Server приема, че ако използвате целочислена константа в клаузата за реда на прозореца, тя представлява порядна позиция на елемент в списъка SELECT, както когато посочите цяло число в клаузата ORDER BY на презентацията. Ако случаят е такъв, друга опция, която си струва да опитате, е да посочите нецелочислена константа, както следва:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No Order') AS n 
FROM dbo.T1;

Оказва се, че това решение също не се поддържа. SQL Server генерира следната грешка:

Msg 5309, ниво 16, състояние 1, ред 65
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат константи като изрази на клауза ORDER BY.

Очевидно клаузата за подреждане на прозореца не поддържа никакъв вид константа.

Досега научихме следното за уместността на подреждането на прозорците на функцията ROW_NUMBER в SQL Server:

  1. Изисква се ORDER BY.
  2. Не може да се подрежда по константа с цяло число, тъй като SQL Server смята, че се опитвате да посочите редна позиция в SELECT.
  3. Не може да се подрежда по никакъв вид константа.

Изводът е, че трябва да подреждате по изрази, които не са константи. Очевидно можете да поръчате по списък с колони от запитаната(ите) таблица(и). Но ние сме в стремеж да намерим ефективно решение, при което оптимизаторът може да разбере, че няма уместност на поръчката.

Постоянно сгъване

Заключението досега е, че не можете да използвате константи в клаузата за ред на прозореца на ROW_NUMBER, но какво да кажем за изрази, базирани на константи, като например в следната заявка:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+0) AS n 
FROM dbo.T1;

Този опит обаче става жертва на процес, известен като постоянно сгъване, който обикновено има положително въздействие върху производителността на заявките. Идеята зад тази техника е да се подобри производителността на заявката чрез сгъване на някакъв израз, базиран на константи, към техните константи на резултата на ранен етап от обработката на заявката. Можете да намерите подробности за това какви видове изрази могат да бъдат постоянно сгънати тук. Нашият израз 1+0 се сгъва до 1, което води до същата грешка, която получавате, когато указвате директно константата 1:

Съобщение 5308, ниво 16, състояние 1, ред 79
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат целочислени индекси като изрази на клауза ORDER BY.

Ще се сблъскате с подобна ситуация, когато се опитвате да съедините два литерала на низ от знаци, като така:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No' + ' Order') AS n 
FROM dbo.T1;

Получавате същата грешка, която сте получили, когато укажете директно литерала „Без поръчка“:

Msg 5309, ниво 16, състояние 1, ред 55
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат константи като изрази на клауза ORDER BY.

Причудлив свят – грешки, които предотвратяват грешки

Животът е пълен с изненади...

Едно нещо, което предотвратява постоянното сгъване е, когато изразът обикновено води до грешка. Например изразът 2147483646+1 може да бъде константен сгънат, тъй като води до валидна стойност, въведена от INT. Следователно опитът за изпълнение на следната заявка е неуспешен:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483646+1) AS n 
FROM dbo.T1;
Съобщение 5308, ниво 16, състояние 1, ред 109
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат целочислени индекси като изрази на клауза ORDER BY.

Изразът 2147483647+1 обаче не може да бъде сгънат постоянен, тъй като такъв опит би довел до грешка при INT-препълване. Изводът от поръчката е доста интересен. Опитайте следната заявка (ще наречем тази заявка 2):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483647+1) AS n 
FROM dbo.T1;

Странно, тази заявка се изпълнява успешно! Това, което се случва е, че от една страна SQL Server не успява да приложи постоянно сгъване и следователно подреждането се основава на израз, който не е единична константа. От друга страна, оптимизаторът смята, че стойността на подреждането е една и съща за всички редове, така че напълно игнорира израза за подреждане. Това се потвърждава при разглеждане на плана за тази заявка, както е показано на Фигура 3.

Фигура 3:План за заявка 2

Забележете, че планът сканира някакъв покриващ индекс със свойство Ordered:False. Точно това беше нашата цел за представяне.

По подобен начин следната заявка включва успешен постоянен опит за сгъване и следователно е неуспешна:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/1) AS n 
FROM dbo.T1;
Msg 5308, ниво 16, състояние 1, ред 123
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат целочислени индекси като изрази на клауза ORDER BY.

Следната заявка включва неуспешен опит за постоянен сгъване и следователно успява, генерирайки плана, показан по-рано на Фигура 3:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/0) AS n 
FROM dbo.T1;

Следната заявка включва успешен опит за постоянно сгъване (литералът на VARCHAR '1' се преобразува имплицитно в INT 1, а след това 1 + 1 се сгъва до 2) и следователно се проваля:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'1') AS n 
FROM dbo.T1;
Съобщение 5308, ниво 16, състояние 1, ред 134
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат целочислени индекси като изрази на клауза ORDER BY.

Следната заявка включва неуспешен опит за постоянно сгъване (не може да преобразува 'A' в INT) и следователно успява, генерирайки плана, показан по-рано на Фигура 3:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'A') AS n 
FROM dbo.T1;

Честно казано, въпреки че тази странна техника постига първоначалната ни цел за изпълнение, не мога да кажа, че я смятам за безопасна и следователно не ми е толкова удобно да разчитам на нея.

Константи по време на изпълнение, базирани на функции

Продължавайки търсенето на добро решение за изчисляване на номера на редове с недетерминиран ред, има няколко техники, които изглеждат по-безопасни от последното странно решение:използване на константи по време на изпълнение въз основа на функции, използване на подзаявка, базирана на константа, използване на колона с псевдоним, базирана на константа и използване на променлива.

Както обяснявам в T-SQL грешки, клопки и най-добри практики – детерминизъм, повечето функции в T-SQL се оценяват само веднъж на препратка в заявката – не веднъж на ред. Това е така дори с повечето недетерминирани функции като GETDATE и RAND. Има много малко изключения от това правило, като функциите NEWID и CRYPT_GEN_RANDOM, които се оценяват веднъж на ред. Повечето функции, като GETDATE, @@SPID и много други, се оценяват веднъж в началото на заявката и техните стойности след това се считат за константи по време на изпълнение. Позоваването на такива функции не се сгъва постоянно. Тези характеристики правят константа по време на изпълнение, която се основава на функция, добър избор като елемент за подреждане на прозорци и наистина изглежда, че T-SQL я поддържа. В същото време оптимизаторът осъзнава, че на практика няма уместност на поръчката, избягвайки ненужни санкции за производителност.

Ето пример за използване на функцията GETDATE:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY GETDATE()) AS n 
FROM dbo.T1;

Тази заявка получава същия план, показан по-рано на Фигура 3.

Ето още един пример за използване на функцията @@SPID (връщане на текущия идентификатор на сесията):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @@SPID) AS n 
FROM dbo.T1;

Какво ще кажете за функцията PI? Опитайте следната заявка:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY PI()) AS n 
FROM dbo.T1;

Този се проваля със следната грешка:

Msg 5309, ниво 16, състояние 1, ред 153
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат константи като изрази на клауза ORDER BY.

Функции като GETDATE и @@SPID се преоценяват веднъж при изпълнение на плана, така че не могат да се сгъват постоянно. PI представлява винаги една и съща константа и следователно се сгъва константа.

Както споменахме по-рано, има много малко функции, които се оценяват веднъж на ред, като NEWID и CRYPT_GEN_RANDOM. Това ги прави лош избор като елемент за подреждане на прозорци, ако имате нужда от недетерминиран ред - да не бъркате с произволен ред. Защо да плащате ненужна санкция за сортиране?

Ето пример за използване на функцията NEWID:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY NEWID()) AS n 
FROM dbo.T1;

Планът за тази заявка е показан на фигура 4, потвърждавайки, че SQL Server е добавил изрично сортиране въз основа на резултата от функцията.

Фигура 4:План за заявка 3

Ако наистина искате номерата на редовете да бъдат присвоени в произволен ред, това е техниката, която искате да използвате. Просто трябва да сте наясно, че това носи разходите за сортиране.

Използване на подзаявка

Можете също да използвате подзаявка, базирана на константа, като израз за подреждане на прозореца (напр. ORDER BY (SELECT 'No Order')). Също така с това решение оптимизаторът на SQL Server признава, че няма релевантност на поръчката и следователно не налага ненужно сортиране или ограничава избора на механизма за съхранение до такива, които трябва да гарантират ред. Опитайте да изпълните следната заявка като пример:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'No Order')) AS n 
FROM dbo.T1;

Получавате същия план, показан по-рано на Фигура 3.

Едно от големите предимства на тази техника е, че можете да добавите своя личен щрих. Може би наистина харесвате NULL:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n 
FROM dbo.T1;

Може би наистина харесвате определено число:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 42)) AS n 
FROM dbo.T1;

Може би искате да изпратите на някого съобщение:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'Lilach, will you marry me?')) AS n 
FROM dbo.T1;

Разбрахте идеята.

Изпълнимо, но неудобно

Има няколко техники, които работят, но са малко неудобни. Единият е да дефинирате псевдоним на колона за израз въз основа на константа и след това да използвате този псевдоним на колона като елемент за подреждане на прозореца. Можете да направите това или с помощта на табличен израз, или с оператора CROSS APPLY и конструктор на стойност на таблица. Ето пример за последното:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY [I'm a bit ugly]) AS n 
FROM dbo.T1 CROSS APPLY ( VALUES('No Order') ) AS A([I'm a bit ugly]);

Получавате същия план, показан по-рано на Фигура 3.

Друга възможност е да използвате променлива като елемент за подреждане на прозореца:

DECLARE @ImABitUglyToo AS INT = NULL;
 
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @ImABitUglyToo) AS n 
FROM dbo.T1;

Тази заявка получава и плана, показан по-рано на Фигура 3.

Ами ако използвам собствен UDF?

Може да си помислите, че използването на вашия собствен UDF, който връща константа, може да бъде добър избор като елемент за подреждане на прозорци, когато искате недетерминиран ред, но не е така. Разгледайте следната дефиниция на UDF като пример:

DROP FUNCTION IF EXISTS dbo.YouWillRegretThis;
GO
 
CREATE FUNCTION dbo.YouWillRegretThis() RETURNS INT
AS
BEGIN
  RETURN NULL
END;
GO

Опитайте да използвате UDF като клауза за подреждане на прозорци, така (ще наречем това Заявка 4):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY dbo.YouWillRegretThis()) AS n 
FROM dbo.T1;

Преди SQL Server 2019 (или ниво на паралелна съвместимост <150), дефинираните от потребителя функции се оценяват на ред. Дори и да върнат константа, те не се вграждат. Следователно, от една страна можете да използвате такъв UDF като елемент за подреждане на прозореца, но от друга страна това води до наказание за сортиране. Това се потвърждава чрез разглеждане на плана за тази заявка, както е показано на Фигура 5.

Фигура 5:План за заявка 4

Започвайки от SQL Server 2019, при ниво на съвместимост>=150, такива дефинирани от потребителя функции се вграждат, което е най-вече страхотно нещо, но в нашия случай води до грешка:

Msg 5309, ниво 16, състояние 1, ред 217
Прозоречни функции, агрегати и функции NEXT VALUE FOR не поддържат константи като изрази на клауза ORDER BY.

Така че използването на UDF на базата на константа като елемент за подреждане на прозореца или принуждава сортиране, или грешка в зависимост от версията на SQL Server, която използвате, и нивото на съвместимост на вашата база данни. Накратко, не правете това.

Номера на разделени редове с недетерминиран ред

Често срещан случай на използване на разделени номера на редове въз основа на недетерминистичен ред е връщането на всеки ред за група. Като се има предвид, че по дефиниция в този сценарий съществува разделящ елемент, бихте си помислили, че безопасна техника в такъв случай би била да се използва елементът за разделяне на прозореца и като елемент за подреждане на прозореца. Като първа стъпка изчислявате номерата на редовете така:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
FROM dbo.T1;

Планът за тази заявка е показан на фигура 6.

Фигура 6:План за заявка 5

Причината, поради която поддържащият ни индекс се сканира със свойство Ordered:True, е, че SQL Server трябва да обработва редовете на всеки дял като едно цяло. Такъв е случаят преди филтрирането. Ако филтрирате само един ред на дял, като опции имате както базирани на поръчка, така и базирани на хеш алгоритми.

Втората стъпка е да поставите заявката с изчисляването на номера на ред в табличен израз и във външната заявка да филтрирате реда с ред номер 1 във всеки дял, както следва:

WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Теоретично тази техника би трябвало да е безопасна, но Пол Уайт откри грешка, която показва, че с помощта на този метод можете да получите атрибути от различни изходни редове в върнатия ред с резултати на дял. Използването на константа по време на изпълнение въз основа на функция или подзаявка, базирана на константа, тъй като елементът за подреждане изглежда е безопасен дори при този сценарий, така че не забравяйте да използвате решение като следното вместо това:

WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY (SELECT 'No Order')) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Никой няма да минава по този път без мое разрешение

Опитът да се изчислят номера на редове въз основа на недетерминистичен ред е често срещана нужда. Би било хубаво, ако T-SQL просто направи клаузата за ред на прозореца незадължителна за функцията ROW_NUMBER, но не го прави. Ако не, би било хубаво, ако поне позволяваше използването на константа като елемент за подреждане, но това също не се поддържа. Но ако попитате добре, под формата на подзаявка, базирана на константа или константа по време на изпълнение, базирана на функция, SQL Server ще го позволи. Това са двете опции, с които се чувствам най-удобно. Наистина не се чувствам комфортно с странните грешни изрази, които изглежда работят, така че не мога да препоръчам тази опция.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Изтеглете копие на вашата база данни

  2. 9-те най-често срещани грешки в дизайна на база данни

  3. Урок за данни:Използване на функциите на прозореца

  4. Свързване на Delphi към Salesforce.com

  5. Нормализиране и производителност на пакетния режим