Основи на табличните изрази, част 2 – Производни таблици, логически съображения
Миналия месец предоставих фон за таблични изрази в T-SQL. Обясних контекста от релационната теория и SQL стандарта. Обясних как една таблица в SQL е опит да се представи релация от релационната теория. Обясних също, че релационният израз е израз, който оперира с една или повече релации като входни данни и води до релация. По същия начин, в SQL изразът за таблица е израз, работещ върху една или повече входни таблици и в резултат на което се получава таблица. Изразът може да бъде заявка, но не е задължително. Например изразът може да бъде конструктор на стойност на таблица, както ще обясня по-късно в тази статия. Обясних също, че в тази серия се фокусирам върху четири специфични типа изрази за именувани таблици, които T-SQL поддържа:извлечени таблици, общи изрази на таблици (CTE), изгледи и вградени функции с стойност на таблица (TVF).
Ако сте работили с T-SQL от известно време, вероятно сте се натъкнали на доста случаи, в които или трябва да използвате таблични изрази, или е било някак по-удобно в сравнение с алтернативни решения, които не ги използват. Ето само няколко примера за случаи на употреба, които ви идват на ум:
Създайте модулно решение, като разбиете сложните задачи на стъпки, всяка от които е представена от различен табличен израз.
Смесване на резултати от групирани заявки и подробности, в случай че решите да не използвате функциите на прозореца за тази цел.
Обработка на логическа заявка обработва клаузите на заявката в следния ред:FROM>WHERE>GROUP BY>HAVING>SELECT>ORDER BY. В резултат на това на същото ниво на влагане псевдоними на колони, които дефинирате в клаузата SELECT, са достъпни само за клаузата ORDER BY. Те не са достъпни за останалите клаузи за заявка. С табличните изрази можете да използвате повторно псевдоними, които дефинирате във вътрешна заявка във всяка клауза на външната заявка и по този начин избягвате повторението на дълги/сложни изрази.
Функциите на прозореца могат да се появяват само в клаузите SELECT и ORDER BY на заявката. С табличните изрази можете да присвоите псевдоним на израз въз основа на функция на прозорец и след това да използвате този псевдоним в заявка срещу израза на таблицата.
Операторът PIVOT включва три елемента:групиране, разпространение и агрегиране. Този оператор идентифицира групиращия елемент имплицитно чрез елиминиране. Използвайки табличен израз, можете да проектирате точно трите елемента, за които се предполага, че участват, и да накарате външната заявка да използва израза на таблицата като входна таблица на оператора PIVOT, като по този начин контролирате кой елемент е групиращият елемент.
Промените с TOP не поддържат клауза ORDER BY. Можете да контролирате кои редове да се избират непряко, като дефинирате табличен израз въз основа на заявка SELECT с филтър TOP или OFFSET-FETCH и клауза ORDER BY и да приложите модификацията спрямо табличния израз.
Това далеч не е изчерпателен списък. Ще демонстрирам някои от горните случаи на употреба и други в тази серия. Просто исках да спомена някои случаи на употреба тук, за да илюстрирам колко важни са табличните изрази в нашия T-SQL код и защо си струва да инвестираме в разбирането на техните основи добре.
В статията от този месец се фокусирам конкретно върху логическото третиране на извлечените таблици.
В моите примери ще използвам примерна база данни, наречена TSQLV5. Можете да намерите скрипта, който го създава и попълва тук, и неговата ER диаграма тук.
Производни таблици
Терминът извлечена таблица се използва в SQL и T-SQL с повече от едно значение. Така че първо искам да изясня кой от тях имам предвид в тази статия. Имам предвид конкретна езикова конструкция, която дефинирате обикновено, но не само, в клаузата FROM на външна заявка. Скоро ще предоставя синтаксиса за тази конструкция.
По-общата употреба на термина извлечена таблица в SQL е аналогът на производна релация от релационната теория. Произведена релация е резултатна връзка, която се извлича от една или повече входни базови релации, чрез прилагане на релационни оператори от релационна алгебра като проекция, пресичане и други към тези базови отношения. По същия начин, в общия смисъл, извлечената таблица в SQL е таблица с резултати, която се извлича от една или повече базови таблици, чрез оценяване на изрази спрямо тези входни базови таблици.
Като настрана проверих как стандартът на SQL дефинира базова таблица и веднага съжалявах, че притесних.
4.15.2 Основни таблици
Базовата таблица е или постоянна базова таблица, или временна таблица.
Постоянната базова таблица е или обикновена постоянна базова таблица, или таблица с версия на системата.
Редовната базова таблица е или обикновена постоянна базова таблица, или временна.“
Добавено тук без повече коментари...
В T-SQL можете да създадете основна таблица с израз CREATE TABLE, но има и други опции, например SELECT INTO и DECLARE @T КАТО ТАБЛИЦА.
Ето дефиницията на стандарта за производни таблици в общия смисъл:
4.15.3 Производни таблици
Произведена таблица е таблица, извлечена директно или непряко от една или повече други таблици чрез оценка на израз, като например <обединена таблица>, <таблица за промяна на данни>, <израз на заявка> или <израз на таблица>. <израз на заявка> може да съдържа по избор <подреждане по клауза>. Подреждането на редовете на таблицата, определено от <израза на заявката>, е гарантирано само за <израза на заявката>, който непосредствено съдържа .”
Тук има няколко интересни неща, които трябва да се отбележат относно производните таблици в общия смисъл. Едното е свързано с коментара относно поръчката. Ще стигна до това по-късно в статията. Друго е, че извлечената таблица в SQL може да бъде валиден самостоятелен табличен израз, но не е задължително. Например, следният израз представлява производна таблица и е също се счита за валиден самостоятелен табличен израз (можете да го стартирате):
ИЗБЕРЕТЕ custid, companynameFROM Sales.CustomersWHERE country =N'USA'
Обратно, следният израз представлява производна таблица, но не е валиден самостоятелен табличен израз:
T1 INNER JOIN T2 ON T1.keycol =T2.keycol
T-SQL поддържа редица таблични оператори, които дават производна таблица, но не се поддържат като самостоятелни изрази. Това са:ПРИСЪЕДИНЕТЕ, ВЪРНЕТЕ, ОТМЕНИТЕ и ПРИЛОЖИТЕ. Имате нужда от клауза, в която да работят (обикновено FROM, но също и клаузата USING на оператора MERGE) и заявка за хост.
Оттук нататък ще използвам термина извлечена таблица, за да опиша по-специфична езикова конструкция, а не в общия смисъл, описан по-горе.
Синтаксис
Произведена таблица може да бъде дефинирана като част от външен оператор SELECT в неговата клауза FROM. Може също да се дефинира като част от операторите DELETE и UPDATE в тяхната клауза FROM и като част от оператор MERGE в неговата клауза USING. Ще предоставя повече подробности за синтаксиса, когато се използва в оператори за модификация по-късно в тази статия.
Ето синтаксиса за опростена заявка SELECT спрямо получена таблица:
SELECT <изберете списък> ОТ ( <израз на таблица> ) [ AS ] <име на таблица>[ (<целеви колони>) ];
Дефиницията на извлечената таблица се появява там, където може нормално да се появи базова таблица, в клаузата FROM на външната заявка. Може да бъде вход за оператор на таблица, като JOIN, APPLY, PIVOT и UNPIVOT. Когато се използва като правилен вход за оператор APPLY, частта
от извлечената таблица има право да има корелации с колони от външна таблица (повече за това в специална бъдеща статия от поредицата). В противен случай табличният израз трябва да бъде самостоятелен.
Външният израз може да има всички обичайни елементи за запитване. В случай на оператор SELECT:WHERE, GROUP BY, HAVING, ORDER BY и както споменахме, таблични оператори в клаузата FROM.
Ето пример за проста заявка към извлечена таблица, представяща клиенти от САЩ:
ИЗБЕРЕТЕ custid, companynameFROM ( SELECT custid, companyname FROM Sales.Customers WHERE country =N'USA' ) КАТО UC;
Има три основни части за идентифициране в израз, включващ дефиниция на производна таблица:
Изразът на таблицата (вътрешната заявка)
Името на извлечената таблица или по-точно това, което в релационната теория се счита за променлива на диапазона
Външното изявление
Изразът на таблицата трябва да представлява таблица и като такъв трябва да удовлетворява определени изисквания, които нормалната заявка не трябва да отговаря непременно. Скоро ще предоставя подробностите в раздела „Изразът на таблица е таблица“.
Що се отнася до името на таблицата, извлечена от целта; често срещано предположение сред разработчиците на T-SQL е, че това е просто име или псевдоним, който присвоявате на целевата таблица. По същия начин, разгледайте следната заявка:
ИЗБЕРЕТЕ custid, companynameFROM Sales.Customers AS CWHERE country =N'USA';
Също така тук общоприетото допускане е, че AS C е просто начин за преименуване или псевдоним на таблицата Клиенти за целите на тази заявка, като се започне от стъпката на обработка на логическата заявка, където се присвоява името и нататък. Въпреки това, от гледна точка на релационната теория, има по-дълбок смисъл в това, което C представлява. C е това, което е известно като променлива на диапазона. C е производна релационна променлива, която се простира над кортежите във входната релационна променлива Клиенти. В горния пример C обхвата кортежите в Customers и оценява предиката държава =N'USA'. Кортежи, за които предикатът се оценява като истина, стават част от резултатната връзка C.
Табличен израз е таблица
С предисторията, която предоставих досега, това, което ще обясня по-нататък, не би трябвало да е изненада. Частта
от дефиниция на производна таблица е таблица . Това е така, дори ако е изразено като заявка. Помните ли свойството на затваряне на релационната алгебра? Същото важи и за останалата част от гореспоменатите именовани таблични изрази (CTE, изгледи и вградени TVF). Както вече научихте, таблицата на SQL е аналогът на отношението на релационната теория , макар и не идеален аналог. По този начин табличният израз трябва да удовлетворява определени изисквания, за да гарантира, че резултатът е таблица – такива, които заявка, която не се използва като табличен израз, не трябва непременно. Ето три специфични изисквания:
Всички колони на табличния израз трябва да имат имена
Всички имена на колони на табличния израз трябва да са уникални
Редовете на табличния израз нямат ред
Нека разделим тези изисквания едно по едно, като обсъдим уместността както за релационната теория, така и за SQL.
Всички колони трябва да имат имена
Не забравяйте, че връзката има заглавие и тяло. Заглавието на релация е набор от атрибути (колони в SQL). Атрибутът има име и име на тип и се идентифицира с името си. Заявка, която не се използва като табличен израз, не трябва непременно да присвоява имена на всички целеви колони. Разгледайте следната заявка като пример:
empid име фамилно име (без име на колона)------ ---------- ---------- ------------- ----1 Сара Дейвис USA/WA/Seattle2 Don Funk USA/WA/Tacoma3 Judy Lew USA/WA/Kirkland4 Yael Peled USA/WA/Redmond5 Sven Mortensen UK/London6 Paul Suurs UK/London7 Russell King UK/London8 Мария Камерън САЩ/WA/Сиатъл9 Патриша Дойл UK/Лондон
Изходът на заявката има анонимна колона, получена от конкатенацията на атрибутите на местоположението с помощта на функцията CONCAT_WS. (Между другото, тази функция беше добавена в SQL Server 2017, така че ако изпълнявате кода в по-ранна версия, не се колебайте да замените това изчисление с алтернативно изчисление по ваш избор.) Следователно тази заявка не върнете таблица, да не говорим за релация. Следователно не е валидно да се използва такава заявка като израз на таблица/част от вътрешна заявка от дефиниция на производна таблица.
Опитайте:
SELECT *FROM ( SELECT empid, име, фамилия, CONCAT_WS(N'/', държава, регион, град) FROM HR.Employees ) AS D;
Получавате следната грешка:
Съобщение 8155, ниво 16, състояние 2, ред 50 Не е посочено име на колона за колона 4 от „D“.
Като настрана забележите ли нещо интересно в съобщението за грешка? Той се оплаква от колона 4, като подчертава разликата между колоните в SQL и атрибутите в релационната теория.
Решението, разбира се, е да се уверите, че изрично присвоявате имена на колони, които са резултат от изчисления. T-SQL поддържа доста техники за именуване на колони. Ще спомена две от тях.
Можете да използвате вградена техника за именуване, при която присвоявате името на целевата колона след изчислението и незадължителна AS клауза, както е в < expression > [ AS ] < column name > , така:
ИЗБЕРЕТЕ empid, име, фамилия, custlocationFROM ( SELECT empid, име, фамилия, CONCAT_WS(N'/', държава, регион, град) КАТО custlocation FROM HR.Employees ) AS D;
Тази заявка генерира следния изход:
empid име фамилно име custlocation------ ---------- ---------- ----------------1 Сара Дейвис USA/WA/Seattle2 Don Funk USA/WA/Tacoma3 Judy Lew USA/WA/Kirkland4 Yael Peled USA/WA/Redmond5 Sven Mortensen UK/London6 Paul Suurs UK/London7 Russell King UK/London8 Мария Камерън САЩ/WA/Seattle9 Патриша Дойл Великобритания/Лондон
Използвайки тази техника, е много лесно, когато преглеждате кода, да разберете кое име на целева колона е присвоено на кой израз. Освен това трябва да наименувате само колони, които все още нямат имена.
Можете също да използвате по-външна техника за именуване на колони, при която посочвате имената на целевите колони в скоби непосредствено след името на извлечената таблица, както следва:
С тази техника обаче трябва да изброите имената за всички колони - включително тези, които вече имат имена. Присвояването на имената на целевите колони се извършва по позиция, отляво надясно, т.е. името на първата целева колона представлява първия израз в списъка SELECT на вътрешната заявка; името на втората целева колона представлява втория израз; и така нататък.
Имайте предвид, че в случай на несъответствие между вътрешните и външните имена на колони, да речем, поради грешка в кода, обхватът на вътрешните имена е вътрешната заявка или по-точно променливата на вътрешния диапазон (тук имплицитно HR.Employees AS Employees) - и обхватът на външните имена е променливата на външния диапазон (D в нашия случай). Има малко повече участие в обхвата на имената на колони, което е свързано с обработката на логическа заявка, но това е елемент за по-късни дискусии.
Потенциалът за грешки с външния синтаксис за именуване се обяснява най-добре с пример.
Разгледайте изхода от предишната заявка с пълния набор от служители от таблицата HR.Employees. След това помислете за следната заявка и преди да я стартирате, опитайте се да разберете кои служители очаквате да видите в резултата:
ИЗБЕРЕТЕ empid, име, фамилия, custlocationFROM ( ИЗБЕРЕТЕ empid, име, фамилия, CONCAT_WS(N'/', държава, регион, град) ОТ HR.Служители КЪДЕ фамилно име LIKE N'D%' ) КАТО D(empid, фамилно име, собствено име, custlocation)КЪДЕ собствено име КАТО N'D%';
Ако очаквате заявката да върне празен набор за дадените примерни данни, тъй като в момента няма служители с фамилно и собствено име, които започват с буквата D, пропускате грешката в кода.
Сега стартирайте заявката и проверете действителния изход:
empid име фамилно име custlocation------ ---------- --------- ---------------1 Дейвис Сара САЩ/WA/Сиатъл9 Дойл Патриша Великобритания/Лондон
Какво се случи?
Вътрешната заявка посочва първо име като втора колона и фамилно име като трета колона в списъка SELECT. Кодът, който присвоява имената на целевите колони на извлечената таблица във външната заявка, посочва второ име и трето име. Кодовите имена първо име като фамилия и фамилия като име в променливата на диапазона D. На практика вие просто филтрирате служители, чието фамилно име започва с буквата D. Вие не филтрирате служители с фамилно и собствено име, които започват с буквата D.
Синтаксисът на вградения псевдоним не е склонен към подобни грешки. От една страна, обикновено не поставяте псевдоним на колона, която вече има име, от което сте доволни. Второ, дори ако искате да зададете различен псевдоним за колона, която вече има име, не е много вероятно със синтаксиса AS да зададете грешен псевдоним. Помисли за това; колко е вероятно да пишете така:
ИЗБЕРЕТЕ empid, име, фамилия, custlocationFROM ( SELECT empid КАТО empid, име КАТО фамилия, фамилия КАТО първо име, CONCAT_WS(N'/', държава, регион, град) КАТО custlocation FROM HR.Служители КЪДЕ фамилия КАТО N'D %' ) КАТО DWHERE първо име КАТО N'D%';
Очевидно, не е много вероятно.
Всички имена на колони трябва да са уникални
Обратно към факта, че заглавието на релация е набор от атрибути и като се има предвид, че атрибутът е идентифициран с име, имената на атрибути трябва да бъдат уникални за същата релация. В дадена заявка винаги можете да се обърнете към атрибут, като използвате име от две части с име на променлива на диапазон като квалификатор, както в <име на променлива на диапазон>.<име на колона>. Когато името на колоната без квалификатора е недвусмислено, можете да пропуснете префикса на името на променливата на диапазона. Това, което е важно да запомните обаче, е това, което казах по-рано за обхвата на имената на колоните. В код, който включва израз на таблица с име, както с вътрешна заявка (изразът на таблицата), така и с външна заявка, обхватът на имената на колоните във вътрешната заявка е променливите на вътрешния диапазон, а обхватът на имената на колоните във външната query са променливите на външния диапазон. Ако вътрешната заявка включва множество изходни таблици с едно и също име на колона, все пак можете да се обърнете към тези колони по недвусмислен начин, като добавите името на променливата на диапазона като префикс. Ако не присвоите изрично име на променлива на диапазона, ще получите такава, присвоена имплицитно, сякаш сте използвали <име на таблица> AS <име на таблица>.
Помислете за следната самостоятелна заявка като пример:
ИЗБЕРЕТЕ C.custid, O.custid, O.orderidFROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid;
Тази заявка не се проваля с грешка в името на дублиращата се колона, тъй като една колона custid всъщност се казва C.custid, а другата O.custid в обхвата на текущата заявка. Тази заявка генерира следния изход:
Опитайте обаче да използвате тази заявка като табличен израз в дефиницията на производна таблица с име CO, така:
SELECT *FROM ( SELECT C.custid, O.custid, O.orderid ОТ Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;
Що се отнася до външната заявка, имате една променлива за диапазон с име CO и обхватът на всички имена на колони във външната заявка е тази променлива на диапазона. Имената на всички колони в дадена променлива на диапазона (не забравяйте, че променливата на диапазона е релационна променлива) трябва да са уникални. Следователно получавате следната грешка:
Съобщение 8156, ниво 16, състояние 1, ред 80 Колоната 'custid' е посочена няколко пъти за 'CO'.
Поправката, разбира се, е да се присвоят различни имена на колони на двете custid колони, що се отнася до променливата на диапазона CO, така:
SELECT *FROM ( SELECT C.custid AS custid, O.custid AS ordercustid, O.orderid ОТ Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;предварително>
Ако следвате добрите практики, изрично изброявате имената на колоните в списъка SELECT на най-външната заявка. Тъй като има само една променлива на диапазона, не е нужно да използвате името от две части за препратките към външните колони. Ако желаете да използвате името от две части, добавяте префикс към имената на колоните с името на променливата за външен диапазон CO, така:
ИЗБЕРЕТЕ CO.custcustid, CO.ordercustid, CO.orderidFROM ( SELECT C.custid AS custcustid, O.custid AS ordercustid, O.orderid ОТ Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;
Няма поръчка
Имам много неща, които трябва да кажа за изразите на именувани таблици и подреждането – достатъчно за отделна статия – така че ще посветя една бъдеща статия на тази тема. Все пак исках да засегна темата накратко тук, тъй като е толкова важна. Припомнете си, че тялото на релация е набор от кортежи и по подобен начин тялото на таблица е набор от редове. Комплектът няма поръчка. Все пак SQL позволява на най-външната заявка да има клауза ORDER BY, обслужваща значение за подреждане на презентацията, както демонстрира следната заявка:
ИЗБЕРЕТЕ orderid, valFROM Sales.OrderValuesORDER BY val DESC;
Това, което трябва да разберете обаче, е, че тази заявка не връща връзка като резултат. Дори от гледна точка на SQL, заявката не връща таблица като резултат и следователно не е се счита за табличен израз. Следователно е невалидно да се използва такава заявка като част от израза на таблица от дефиниция на производна таблица.
Опитайте да изпълните следния код:
ИЗБЕРЕТЕ идентификатор на поръчката, valFROM ( SELECT orderid, val FROM Sales.OrderValues ORDER BY val DESC ) КАТО D;
Получавате следната грешка:
Съобщение 1033, ниво 15, състояние 1, ред 124 Клаузата ORDER BY е невалидна в изгледи, вградени функции, производни таблици, подзаявки и изрази за общи таблици, освен ако също не е указано TOP, OFFSET или FOR XML.
Ще се обърна към освен ако част от съобщението за грешка скоро.
Ако искате най-външната заявка да върне подреден резултат, трябва да посочите клаузата ORDER BY в най-външната заявка, както следва:
ИЗБЕРЕТЕ orderid, valFROM ( SELECT orderid, val FROM Sales.OrderValues ) КАТО DORDER BY val DESC;
Що се отнася до освен част от съобщението за грешка; T-SQL поддържа собствения TOP филтър, както и стандартния OFFSET-FETCH филтър. И двата филтъра разчитат на клауза ORDER BY в същия обхват на заявката, за да дефинират за тях кои горни редове да филтрират. Това за съжаление е резултат от капан в дизайна на тези функции, който не отделя подреждането на презентацията от подреждането на филтъра. Както и да е, както Microsoft с неговия TOP филтър, така и стандартът с неговия филтър OFFSET-FETCH, позволяват посочване на клауза ORDER BY във вътрешната заявка, стига да посочва също филтъра TOP или OFFSET-FETCH, съответно. Така че тази заявка е валидна, например:
ИЗБЕРЕТЕ идентификатор на поръчката, valFROM ( SELECT TOP (3) orderid, val FROM Sales.OrderValues ORDER BY val DESC ) КАТО D;
Когато изпълних тази заявка в моята система, тя генерира следния изход:
Това, което е важно да се подчертае, е, че единствената причина, поради която клаузата ORDER BY е разрешена във вътрешната заявка, е да поддържа филтъра TOP. Това е единствената гаранция, която получавате, що се отнася до поръчката. Тъй като външната заявка също няма клауза ORDER BY, вие не получавате гаранция за специфично подреждане на презентация от тази заявка, въпреки каквото и да е наблюдаваното поведение. Това е както в T-SQL, така и в стандарта. Ето цитат от стандарта, отнасящ се до тази част:
„Подреждането на редовете на таблицата, определено от <израза на заявката>, е гарантирано само за <израза на заявката>, който непосредствено съдържа <подреждане по клауза>.”
Както споменахме, има много повече да се каже за изразите на таблицата и подреждането, което ще направя в бъдеща статия. Ще дам също примери, демонстриращи как липсата на клауза ORDER BY във външната заявка означава, че не получавате никакви гаранции за подреждане на презентация.
И така, табличен израз, например вътрешна заявка в дефиниция на производна таблица, е таблица. По подобен начин самата извлечена таблица (в специфичния смисъл) също е таблица. Това не е основна маса, но въпреки това е маса. Същото важи и за CTE, изгледи и вградени TVF. Те не са базови таблици, а по-скоро извлечени (в по-общия смисъл), но въпреки това са таблици.
Недостатъци в дизайна
Производните таблици имат два основни недостатъка в дизайна си. И двете са свързани с факта, че извлечената таблица е дефинирана в клаузата FROM на външната заявка.
Един недостатък в дизайна е свързан с факта, че ако трябва да направите заявка за производна таблица от външна заявка и от своя страна да използвате тази заявка като израз на таблица в друга дефиниция на производна таблица, в крайна сметка вмъквате тези заявки на производна таблица. При изчисленията изричното влагане на код, включващо множество нива на влагане, обикновено води до сложен код, който е труден за поддържане.
Ето един много основен пример, демонстриращ това:
ИЗБЕРЕТЕ година на поръчка, numcustsFROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) КАТО D1 ГРУПА ПО година на поръчка ) AS D2WHERE numcusts> 70;
Този код връща годините на поръчките и броя на клиентите, които са направили поръчки през всяка година, само за години, в които броят на клиентите, които са направили поръчки, е бил по-голям от 70.
Основната мотивация за използване на таблични изрази тук е да може да се препраща към псевдоним на колона многократно. Най-вътрешната заявка, използвана като табличен израз за извлечената таблица D1, отправя заявка към таблицата Sales.Orders и присвоява името на колоната orderyear на израза YEAR(orderdate), а също така връща колоната custid. Заявката към D1 групира редовете от D1 по година на поръчка и връща година на поръчка, както и отделния брой клиенти, които са направили поръчки през въпросната година, под псевдонима numcusts. Кодът дефинира производна таблица, наречена D2, въз основа на тази заявка. Най-външната заявка от заявките D2 и филтрира само години, в които броят на клиентите, които са направили поръчки, е бил по-голям от 70.
Опитът да прегледате този код или да го отстраните в случай на проблеми е труден поради множеството нива на влагане. Вместо да преглеждате кода по по-естествения начин отгоре до долу, вие се налага да го анализирате, като започнете от най-вътрешната единица и постепенно вървите навън, тъй като това е по-практично.
Целият смисъл на използването на извлечени таблици в този пример беше да се опрости кодът, като се избягва необходимостта от повтаряне на изрази. Но не съм сигурен, че това решение постига тази цел. В този случай вероятно е по-добре да повторите някои изрази, като избягвате необходимостта да използвате извлечени таблици като цяло, като така:
ИЗБЕРЕТЕ ГОДИНА(дата на поръчка) КАТО година на поръчка, COUNT(DISTINCT custid) AS numcustsFROM Sales.OrdersGROUP BY YEAR(orderdate)HAVING COUNT(DISTINCT custid)> 70;
Имайте предвид, че тук показвам много прост пример за илюстрация. Представете си производствен код с повече нива на вложеност и с по-дълъг, по-сложен код и можете да видите как става значително по-сложен за поддръжка.
Друг недостатък в дизайна на производни таблици е свързан със случаите, в които трябва да взаимодействате с множество екземпляри на една и съща извлечена таблица. Разгледайте следната заявка като пример:
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diffFROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS CUR LEFT OUTER JOIN ( ИЗБЕРЕТЕ ГОДИНА (дата на поръчка) КАТО година на поръчка, COUNT(*) КАТО номера на поръчки ОТ Sales.Orders ГРУПА ПО ГОДИНА(дата на поръчка) ) КАТО PRV ON CUR.orderyear =PRV.orderyear + 1;
Този код изчислява броя на обработените поръчки за всяка година, както и разликата спрямо предходната година. Игнорирайте факта, че има по-прости начини за постигане на същата задача с функциите на прозореца — използвам този код, за да илюстрирам определен момент, така че самата задача и различните начини за решаването й не са от значение.
Обединяването е оператор на таблица, който третира двата си входа като набор - което означава, че няма ред между тях. Те се наричат ляв и десен вход, така че можете да маркирате един от тях (или и двете) като запазена таблица във външно съединение, но все пак няма първо и второ сред тях. Разрешено е да използвате извлечени таблици като входни данни за обединяване, но името на променливата на диапазона, което присвоите на левия вход, не е достъпно в дефиницията на десния вход. Това е така, защото и двете са концептуално дефинирани в една и съща логическа стъпка, сякаш в един и същи момент от време. Следователно, когато обединявате извлечени таблици, не можете да дефинирате две променливи на диапазона на базата на един табличен израз. За съжаление трябва да повторите кода, дефинирайки две променливи на диапазона въз основа на две идентични копия на кода. Това разбира се усложнява поддръжката на кода и увеличава вероятността от грешки. Всяка промяна, която правите в един табличен израз, трябва да се приложи и към другия.
Както ще обясня в една бъдеща статия, CTE в своя дизайн не понасят тези два недостатъка, които възникват на извлечените таблици.
Конструктор на стойности на таблица
Конструктор на стойност на таблица ви позволява да конструирате стойност на таблица въз основа на самостоятелни скаларни изрази. След това можете да използвате такава таблица във външна заявка, точно както използвате производна таблица, която се основава на вътрешна заявка. В една бъдеща статия обсъждам странично извлечени таблици и корелациите в детайли и ще покажа по-сложни форми на конструктори за стойности на таблица. In this article, though, I’ll focus on a simple form that is based purely on self-contained scalar expressions.
The general syntax for a query against a table value constructor is as follows: