Стандартът ISO/IEC 9075:2016 (SQL:2016) дефинира функция, наречена функции на вложени прозорци. Тази функция ви позволява да вложите два вида прозоречни функции като аргумент на прозоречна агрегатна функция. Идеята е да ви позволи да се позовавате или на номер на ред, или на стойност на израз, при стратегически маркери в елементите на прозореца. Маркерите ви дават достъп до първия или последния ред в дяла, първия или последния ред в рамката, текущия външен ред и текущия ред на рамката. Тази идея е много мощна, като ви позволява да прилагате филтриране и други видове манипулации във вашата функция на прозореца, които понякога са трудни за постигане по друг начин. Можете също да използвате функции на вложени прозорци, за да емулирате лесно други функции, като рамки, базирани на RANGE. Тази функция в момента не е налична в T-SQL. Публикувах предложение за подобряване на SQL Server чрез добавяне на поддръжка за функции на вложени прозорци. Не забравяйте да добавите своя глас, ако смятате, че тази функция може да ви бъде от полза.
За какво не са функциите на вложените прозорци
Към датата на това писане няма много налична информация за истинските стандартни функции на вложени прозорци. Това, което прави по-трудно, е, че все още не знам никоя платформа, която да внедри тази функция. Всъщност, стартирането на уеб търсене за вложени функции на прозорец връща предимно покритие и дискусии за вложени групирани агрегатни функции в рамките на прозоречни агрегатни функции. Да предположим например, че искате да направите заявка за изгледа Sales.OrderValues в примерната база данни на TSQLV5 и да върнете за всеки клиент и дата на поръчка дневната сума на стойностите на поръчката и текущата сума до текущия ден. Такава задача включва както групиране, така и прозорец. Групирате редовете по идентификационния номер на клиента и датата на поръчката и прилагате текуща сума върху груповата сума от стойностите на поръчката, както следва:
ИЗПОЛЗВАЙТЕ TSQLV5; -- http://tsql.solidq.com/SampleDatabases/TSQLV5.zip ИЗБЕРЕТЕ custid, orderdate, SUM(val) КАТО обща сума за деня, SUM(SUM(val)) НАД (PARTITION BY custid ORDER BY orderdate РЕДОВЕ НЕОГРАНИЧЕНИ ПРЕДШЕСТВАЩА) КАТО текуща сума FROM Sales.OrderValues GROUP BY custid, orderdate;
Тази заявка генерира следния изход, показан тук в съкратена форма:
custid orderdate day total runsum ------- ---------- --------- ---------- 1 2018-08-25 814,50 814,50 1 2018-10-03 878.00 1692.50 1 2018-10-13 330.00 2022.50 1 2019-01-15 845.80 2868.30 1 2019-03-16 471.20 3339.50 1 2019-04-09 933.50 4273.00 2 2017-09-18 88.80 88.80 2 2018 -08-08 479,75 568,55 2 2018-11-28 320,00 888,55 2 2019-03-04 514,40 1402,95 ...
Въпреки че тази техника е доста готина и въпреки че търсенето в мрежата за функции на вложени прозорци връщат предимно такива техники, това не означава стандартът на SQL под функциите на вложени прозорци. Тъй като не можах да намеря никаква информация по темата, просто трябваше да я разбера от самия стандарт. Надяваме се, че тази статия ще повиши осведомеността за функцията за истински вложени прозорци и ще накара хората да се обърнат към Microsoft и да поискат да добавят поддръжка за нея в SQL Server.
За какво се отнасят функциите за вложени прозорци
Функциите на вложените прозорци включват две функции, които можете да вложите като аргумент на агрегатна функция на прозореца. Това са вложената функция за номер на ред и вложеният израз value_of във функцията за ред.
Вложена функция за номер на ред
Функцията за вложен номер на ред ви позволява да се обърнете към номера на редовете на стратегическите маркери в елементите на прозореца. Ето синтаксиса на функцията:
Маркерите на редове, които можете да посочите, са:
- BEGIN_PARTITION
- END_PARTITION
- BEGIN_FRAME
- END_FRAME
- CURRENT_ROW
- FRAME_ROW
Първите четири маркера се разбират сами. Що се отнася до последните две, маркерът CURRENT_ROW представлява текущия външен ред, а FRAME_ROW представлява текущия вътрешен ред на рамката.
Като пример за използване на функцията за номер на вложен ред, разгледайте следната задача. Трябва да направите заявка за изглед Sales.OrderValues и да върнете за всяка поръчка някои от нейните атрибути, както и разликата между текущата стойност на поръчката и средната стойност на клиента, но с изключение на първата и последната клиентска поръчка от средната стойност.
Тази задача е постижима без функции на вложени прозорци, но решението включва доста стъпки:
С C1 AS ( ИЗБЕРЕТЕ custid, val, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY orderdate, orderid ) КАТО rownumasc, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY order date DESC, FROM Sared DESCles.c) OrderValues ), C2 AS ( SELECT custid, AVG(val) AS avgval FROM C1 WHERE 1 NOT IN (rownumasc, rownumdesc) GROUP BY custid ) SELECT O.orderid, O.custid, O.orderdate, O.val, O.val. - C2.avgval AS diff FROM Sales.OrderValues AS O LEFT OUTER JOIN C2 ON O.custid =C2.custid;
Ето изхода от тази заявка, показан тук в съкратена форма:
orderid custid orderdate val diff -------- ------- ---------- -------- --------- --- 10411 10 2018-01-10 966.80 -570.184166 10743 4 2018-11-17 319.20 -809.813636 11075 68 2019-05-06 498.10 -1546.297500 10388 72 2017-12-19 1228.80 -358.864285 10720 61 2018-10-28 550.00 -144.744285 11052 34 2019-04-27 1332.00 -1164.397500 10457 39 2018-02-25 1584.00 -797.999166 10789 23 2018-12-22 3687.00 1567.833334 10434 24 2018-02-03 321.12 -1329.582352 10766 56 2018-12-05 2310.00 1015.105000 ...
Използвайки вложени функции за номера на редове, задачата е постижима с една заявка, както следва:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN ROW_NUMBER(FRAME_ROW) NOT IN ( ROW_NUMBER(BEGIN_PARTITION), ROW_NUMBER(END_PARTITION) ) THEN val END ) OVER( PARTITION BY CUSTID date ORDER, order ID МЕЖДУ НЕОГРАНИЧЕН ПРЕДШЕСТВАЩ И НЕОГРАНИЧЕН СЛЕД ) КАТО разл. ОТ Продажби.Стойности на поръчката;
Също така, поддържаното в момента решение изисква поне едно сортиране в плана и множество преминавания върху данните. Решението, използващо вложени функции за номера на редове, има целия потенциал да бъде оптимизирано с разчитане на реда на индекси и намален брой преминавания над данните. Това, разбира се, зависи от изпълнението.
Вложен израз на стойност_на във функцията на ред
Вложената функция value_of израз в ред ви позволява да взаимодействате със стойност на израз при същите стратегически маркери на редове, споменати по-рано в аргумент на агрегатна функция на прозореца. Ето синтаксиса на тази функция:
СТОЙНОСТ НА <израз> AT <маркер на ред> [
>) НАД (<спецификация>)
Както можете да видите, можете да посочите определена отрицателна или положителна делта по отношение на маркера на ред и по избор да предоставите стойност по подразбиране, в случай че ред не съществува на посочената позиция.
Тази способност ви дава много сила, когато трябва да взаимодействате с различни точки в елементите на прозореца. Помислете за факта, че толкова мощни, колкото функциите на прозореца могат да бъдат сравнени с алтернативни инструменти като подзаявки, това, което функциите на прозореца не поддържат, е основна концепция за корелация. С помощта на маркера CURRENT_ROW получавате достъп до външния ред и по този начин емулирате корелации. В същото време можете да се възползвате от всички предимства, които функциите на прозореца имат в сравнение с подзаявките.
Като пример, да предположим, че трябва да направите заявка за изгледа Sales.OrderValues и да върнете за всяка поръчка някои от нейните атрибути, както и разликата между текущата стойност на поръчката и средната стойност на клиента, но с изключение на поръчките, направени на същата дата като текущата дата на поръчката. Това изисква способност, подобна на корелация. С вложената функция value_of в ред, използвайки маркера CURRENT_ROW, това е лесно постижимо по следния начин:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues;
Тази заявка трябва да генерира следния изход:
orderid custid orderdate val diff -------- ------- ---------- -------- --------- --- 10248 85 2017-07-04 440.00 180.000000 10249 79 2017-07-05 1863.40 1280.452000 10250 34 2017-07-08 1552.60 -854.228461 10251 84 2017-07-08 654.06 -293.536666 10252 76 2017-07-09 3597.90 1735.092728 10253 34 2017-07-10 1444.80 -970.320769 10254 14 2017-07-11 556.62 -1127.988571 10255 68 2017-07-12 2490.50 617.913334 10256 88 2017-07-15 517.80 -176.000000 10257 35 2017-07-16 1119.90 -153.562352 . ..
Ако смятате, че тази задача е постижима също толкова лесно с корелирани подзаявки, в този опростен случай ще сте прави. Същото може да се постигне със следната заявка:
ИЗБЕРЕТЕ O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( ИЗБЕРЕТЕ AVG(O2.val) ОТ Sales.OrderValues КАТО O2 КЪДЕТО O2.custid =O1.custid И O2.orderdate <> O1.orderdate ) AS diff FROM Sales.OrderValues AS O1;
Не забравяйте обаче, че подзаявката работи с независим изглед на данните, докато функцията на прозореца работи с набора, който се предоставя като вход за стъпката за обработка на логическа заявка, която обработва клаузата SELECT. Обикновено основната заявка има допълнителна логика като свързвания, филтри, групиране и други. С подзаявките трябва или да подготвите предварителен CTE, или да повторите логиката на основната заявка също в подзаявката. С функциите на прозореца няма нужда да повтаряте логиката.
Например, кажете, че е трябвало да работите само с изпратени поръчки (където датата на доставка не е NULL), които са били обработени от служител 3. Решението с функцията прозорец трябва да добави предикатите на филтъра само веднъж, както следва:
ИЗБЕРЕТЕ orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues WHERE OTIS empid NULL;
Тази заявка трябва да генерира следния изход:
orderid custid orderdate val diff -------- ------- ---------- -------- --------- ---- 10251 84 2017-07-08 654.06 -459.965000 10253 34 2017-07-10 1444.80 531.733334 10256 88 2017-07-15 517.80 -1022.020000 10266 87 2017-07-26 346.56 NULL 10273 63 2017-08-05 2037.28 -3149.075000 10283 46 2017-08-16 1414.80 534.300000 10309 37 2017-09-19 1762.00 -1951.262500 10321 38 2017-10-03 144.00 NULL 10330 46 2017-10-16 1649.00 885.600000 10332 51 2017-10-17 1786.88 495.830000 .. .
Решението с подзаявката трябва да добави предикати на филтъра два пъти – веднъж във външната заявка и веднъж в подзаявката – така:
ИЗБЕРЕТЕ O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( ИЗБЕРЕТЕ AVG(O2.val) ОТ Sales.OrderValues КАТО O2 КЪДЕТО O2.custid =O1.custid И O2.orderdate <> O1.orderdate AND empid =3 AND shippeddate НЕ Е NULL) AS diff FROM Sales.OrderValues КАТО O1 КЪДЕТО empid =3 И shippeddate НЕ Е NULL;
Това е или това, или добавяне на предварителен CTE, който се грижи за цялото филтриране и всяка друга логика. Както и да го погледнете, с подзаявките има по-сложни слоеве.
Другото предимство на функциите за вложени прозорци е, че ако имахме поддръжка за тези в T-SQL, би било лесно да емулираме липсващата пълна поддръжка за модула RANGE прозорец рамка. Предполага се, че опцията RANGE ви позволява да дефинирате динамични кадри, които се основават на отместване от стойността за подреждане в текущия ред. Например, да предположим, че трябва да изчислите за всяка клиентска поръчка от изгледа Sales.OrderValues плъзгащата се средна стойност за последните 14 дни. Съгласно стандарта SQL, можете да постигнете това с помощта на опцията RANGE и типа INTERVAL, както следва:
ИЗБЕРЕТЕ идентификатор на поръчка, custid, orderdate, val, AVG(val) OVER( PARTITION BY custid ORDER BY orderdate RANGE МЕЖДУ ИНТЕРВАЛ '13' ДЕН ПРЕДШЕСТВУВАЩ И ТЕКУЩ РЕД ) КАТО движещ сеavg14days FROM Sales.OrderValuТази заявка трябва да генерира следния изход:
orderid custid orderdate val moveavg14days -------- ------- ---------- ------- ---------- ----- 10643 1 2018-08-25 814.50 814.500000 10692 1 2018-10-03 878.00 878.000000 10702 1 2018-10-13 330.00 604.000000 10835 1 2019-01-15 845.80 845.800000 10952 1 2019-03-16 471.20 471.200000 11011 1 2019-04-09 933.50 933.500000 10308 2 2017-09-18 88.80 88.800000 10625 2 2018-08-08 479.75 479.750000 10759 2 2018-11-28 320.00 320.000000 10926 2 2019-03-04 514.40 514.400000 10365 3 2017-11 -27 403.20 403.200000 10507 3 2018-04-15 749.06 749.060000 10535 3 2018-05-13 1940.85 1940.850000 10573 3 2018-06-19 2082.00 2082.000000 10677 3 2018-09-22 813.37 813.370000 10682 3 2018-09-25 375.50 594.435000 10856 3 2019-01-28 660,00 660,000000 ...Към датата на писане този синтаксис не се поддържа в T-SQL. Ако имахме поддръжка за функции на вложени прозорци в T-SQL, щяхте да можете да емулирате тази заявка със следния код:
ИЗБЕРЕТЕ orderid, custid, orderdate, val, AVG( CASE WHEN DATEDIFF(day, orderdate, VALUE OF orderdate AT CURRENT_ROW) МЕЖДУ 0 И 13 THEN val END ) OVER( PARTITION BY CUSTID ORDER BY CUSTID ORDER BY CUSTID ORDER BY CUSTID ORDER BY CUSTID ORDER BY CUSTID ORDER BY CUSTID ORDER BY CUSTID ORDER BY CUSTID ORDER BY UNECBODATE DAY) ПРЕМЕСТВАНЕ ЗА ПРЕДВАРИТЕЛЕН ПОРЪЧКА ОТ Sales.OrderValues;Какво не ви харесва?
Гласувайте
Стандартните функции за вложени прозорци изглеждат като много мощна концепция, която позволява голяма гъвкавост при взаимодействие с различни точки в елементите на прозореца. Доста съм изненадан, че не мога да намеря никакво покритие на концепцията освен в самия стандарт и че не виждам много платформи, които го прилагат. Надяваме се, че тази статия ще повиши осведомеността за тази функция. Ако смятате, че би било полезно за вас да го имате на разположение в T-SQL, не забравяйте да гласувате!