Това е 9-та част от поредица за изрази за именувани таблици. В част 1 предоставих фона на изразите на именувани таблици, които включват извлечени таблици, общи изрази на таблици (CTE), изгледи и вградени функции с стойност на таблица (iTVF). В част 2, част 3 и част 4 се фокусирах върху производни таблици. В част 5, част 6, част 7 и част 8 се фокусирах върху CTE. Както обясних, извлечените таблици и CTE са изрази с именувани таблици с обхват на изрази. След като изявлението, което ги дефинира, приключи, те са изчезнали.
Вече сме готови да пристъпим към покриване на многократно използвани именувани таблични изрази. Тоест такива, които са създадени като обект в базата данни и остават там за постоянно, освен ако не бъдат изпуснати. Като такива, те са достъпни и могат да се използват повторно за всички, които имат правилните разрешения. Прегледите и iTVF попадат в тази категория. Разликата между двете е преди всичко, че първият не поддържа входни параметри, а вторият го поддържа.
В тази статия започвам отразяването на мненията. Както направих преди, първо ще се съсредоточа върху логически или концептуални аспекти, а на по-късен етап ще пристъпя към аспекти на оптимизация. С първата статия за изгледите искам да започна леко, като се съсредоточа върху това какво е изглед, като използвам правилната терминология и сравнявам съображенията за проектиране на изгледите с тези на обсъжданите по-рано извлечени таблици и CTE.
В моите примери ще използвам примерна база данни, наречена TSQLV5. Можете да намерите скрипта, който го създава и попълва тук, и неговата диаграма за ER тук.
Какво е изглед?
Както обикновено, когато обсъждаме релационната теория, ние, практикуващите SQL, често ни казват, че терминологията, която използваме, е грешна. Така че, в този дух, веднага ще започна с това, че когато използвате термина таблици и изгледи , грешно е. Научих това от Крис Дейт.
Припомнете си, че таблицата е аналог на SQL на релация (опростявайки малко дискусията около стойности и променливи). Таблицата може да бъде базова таблица, дефинирана като обект в базата данни, или може да бъде таблица, върната от израз – по-точно израз на таблица. Това е подобно на факта, че релация може да бъде тази, която се връща от релационен израз. Таблицен израз може да бъде заявка.
Сега, какво е изглед? Това е израз на именувана таблица, подобно на CTE е израз на именувана таблица. Просто, както казах, изгледът е многократно използван израз на именувана таблица, който се създава като обект в базата данни и е достъпен за тези, които имат правилните разрешения. Това е всичко да се каже, изгледът е маса. Това не е основна маса, но въпреки това маса. Така че точно както казването „правоъгълник и квадрат“ или „уиски и лагавулин“ би изглеждало странно (освен ако не сте имали твърде много лагавулин!), използването на „таблици и изгледи“ е също толкова неправилно.
Синтаксис
Ето синтаксиса на T-SQL за израз CREATE VIEW:
СЪЗДАЙТЕ [ИЛИ ПРОМЕНИ] ИЗГЛЕД [<име на схема>. ] <име на таблица> [ (<целеви колони>) ][ С <атрибути на преглед, включително SCHEMABINDING> ]
AS
<израз на таблица>
[ С ОПЦИЯ НА ПРОВЕРКА ]
[; ]
Инструкцията CREATE VIEW трябва да бъде първата и единствена инструкция в групата.
Имайте предвид, че частта CREATE OR ALTER е въведена в SQL Server 2016 SP1, така че ако използвате по-ранна версия, ще трябва да работите с отделни изрази CREATE VIEW и ALTER VIEW в зависимост от това дали обектът вече съществува или не. Както вероятно добре знаете, промяната на съществуващ обект запазва присвоените разрешения. Това е една от причините, поради които обикновено е разумно да промените съществуващ обект, вместо да го изпуснете и пресъздадете. Това, което изненада някои хора, е, че промяната на изглед не запазва съществуващите атрибути на изглед; те трябва да бъдат препосочени, ако искате да ги запазите.
Ето пример за проста дефиниция на изглед, представляваща клиенти от САЩ:
ИЗПОЛЗВАЙТЕ TSQLV5;GO СЪЗДАЙТЕ ИЛИ ПРОМЕНЯТЕ ПРЕГЛЕД Sales.USACustomersAS ИЗБЕРЕТЕ custid, име на фирма ОТ Sales.Customers WHERE country =N'USA';GO
И ето изявление, което отправя заявка към изгледа:
ИЗБЕРЕТЕ custid, companynameFROM Sales.USACcustomers;
Между израза, който създава изгледа, и оператора, който го отправя към него, ще намерите същите три елемента, които участват в оператор срещу извлечена таблица или CTE:
- Изразът на вътрешната таблица (вътрешната заявка на изгледа)
- Присвоеното име на таблица (името на изгледа)
- Изявлението с външната заявка срещу изгледа
Тези от вас с набито око ще забележат, че тук всъщност има два таблични израза. Има вътрешната (вътрешната заявка на изгледа) и има външната (заявката в изявлението срещу изгледа). В израза със заявката срещу изгледа самата заявка е табличен израз и след като добавите терминатора, той става израз. Това може да звучи придирчиво, но ако разберете това и наречете нещата с правилните им имена, това рефлектира върху знанията ви. И не е ли страхотно, когато знаеш, че знаеш?
Също така, всички изисквания от табличния израз в производни таблици и CTE, които обсъдихме по-рано в поредицата, се прилагат към табличния израз, на който се базира изгледът. Напомняме, че изискванията са:
- Всички колони на табличния израз трябва да имат имена
- Всички имена на колони на табличния израз трябва да са уникални
- Редовете на табличния израз нямат ред
Ако трябва да опресните разбирането си за това, което стои зад тези изисквания, вижте раздела „Изразът на таблица е таблица“ в част 2 от поредицата. Уверете се, че разбирате особено частта „без поръчка“. Като кратко напомняне, табличният израз е таблица и като такъв няма ред. Ето защо не можете да създадете изглед въз основа на заявка с клауза ORDER BY, освен ако тази клауза не поддържа филтър TOP или OFFSET-FETCH. И дори с това изключение, което позволява на вътрешната заявка да има клауза ORDER BY, искате да запомните, че ако външната заявка към изгледа няма своя собствена клауза ORDER BY, вие не получавате гаранция, че заявката ще се върне редовете в определен ред, без значение наблюдаваното поведение. Това е изключително важно да се разбере!
Вложение и множество препратки
Когато обсъждахме съображенията за проектиране на извлечени таблици и CTE, сравних двете по отношение както на влагане, така и на множество препратки. Сега нека видим как се представят изгледите в тези отдели. Ще започна с гнездене. За тази цел ще сравним код, който връща години, през които повече от 70 клиенти са направили поръчки, използвайки извлечени таблици, CTE и изгледи. Вече видяхте кода с извлечени таблици и CTE по-рано в поредицата. Ето кода, който обработва задачата с помощта на производни таблици:
ИЗБЕРЕТЕ година на поръчка, numcustsFROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) КАТО D1 ГРУПА ПО година на поръчка ) AS D2WHERE numcusts> 70;Посочих, че основният недостатък, който виждам при извлечените таблици тук, е фактът, че вмъквате дефиниции на извлечени таблици и това може да доведе до сложност при разбирането, поддържането и отстраняването на неизправности в такъв код.
Ето кода, който обработва същата задача с помощта на CTEs:
С C1 AS( ИЗБЕРЕТЕ ГОДИНА (дата на поръчка) КАТО година на поръчка, custid FROM Sales.Orders),C2 AS( SELECT order year, COUNT(DISTINCT custid) AS numcusts ОТ C1 GROUP BY orderyear)SELECT year на поръчка, numcustsFROM C2WHERE numcusts>Посочих, че това ми се струва много по-ясен код поради липсата на влагане. Можете да видите всяка стъпка в решението от началото до края поотделно в отделна единица, като логиката на решението тече ясно отгоре надолу. Ето защо аз виждам опцията CTE като подобрение спрямо извлечените таблици в това отношение.
Сега към изгледите. Не забравяйте, че едно от основните предимства на изгледите е възможността за повторна употреба. Можете също да контролирате разрешенията за достъп. Развитието на участващите единици е малко по-подобно на CTEs в смисъл, че можете да фокусирате вниманието си върху една единица в даден момент от началото до края. Освен това имате гъвкавостта да решите дали да създадете отделен изглед за единица в решението, или може би само един изглед въз основа на заявка, включваща изрази за именувани таблици с обхват на изрази.
Бихте използвали първия, когато всеки от модулите трябва да бъде повторно използван. Ето кода, който бихте използвали в такъв случай, създавайки три изгледа:
-- Sales.OrderYearsCREATE OR ALTER VIEW Sales.OrderYearsAS SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders;GO -- Sales.YearlyCustCountsCREATE OR ALTER VIEW Sales.YearlyCustCounts. ОТ Sales.OrderYears ГРУПА ПО година на поръчка;GO -- Sales.YearlyCustCountsMin70СЪЗДАВАНЕ ИЛИ ПРОМЕНЯНЕ НА ИЗГЛЕЖДАНЕ Sales.YearlyCustCountsAbove70AS ИЗБЕРЕТЕ година на поръчка, numcusts ОТ Sales.YearlyCustCounts WHERE numcusts>>Можете да направите заявка за всеки един от изгледите поотделно, но ето кода, който бихте използвали, за да върнете това, след което е била първоначалната задача.
ИЗБЕРЕТЕ година на поръчка, numcustsFROM Sales.YearlyCustCountsAbove70;Ако има изискване за повторна употреба само за най-външната част (каквото изискваше първоначалната задача), няма реална нужда да се разработват три различни изгледа. Можете да създадете един изглед въз основа на заявка, включваща CTE или производни таблици. Ето как бихте направили това със заявка, включваща CTEs:
СЪЗДАДЕТЕ ИЛИ ПРОМЕНЯТЕ ПРЕГЛЕД Sales.YearlyCustCountsAbove70AS С C1 AS ( ИЗБЕРЕТЕ ГОДИНА (дата на поръчка) КАТО година на поръчка, custid FROM Sales.Orders ), C2 AS ( ИЗБЕРЕТЕ година на поръчка, COUNT(DISTINCT custid) КАТО BROJ ГОДИНА (ДАТА на поръчка) ИЗБЕРЕТЕ ГОДИНА на поръчка C1 , numcusts ОТ C2 КЪДЕ numcusts> 70;GOМежду другото, ако не беше очевидно, CTE, на които се основава вътрешната заявка на изгледа, могат да бъдат рекурсивни.
Нека преминем към случаите, в които имате нужда от множество препратки към един и същ табличен израз от външната заявка. Задачата за този пример е да се изчисли броят на годишните поръчки за година и да се сравни броят за всяка година с предходната година. Най-лесният начин да постигнете това всъщност е да използвате функцията на прозореца на LAG, но ще използваме свързване между два екземпляра на табличен израз, представляващ броя на годишните поръчки, само за да сравним случай с множество препратки между трите инструмента.
Това е кодът, който използвахме по-рано в поредицата, за да се справим със задачата с производни таблици:
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;Тук има много ясен недостатък. Трябва да повторите дефиницията на табличния израз два пъти. По същество дефинирате два именувани таблични израза въз основа на един и същ код на заявка.
Ето кода, който обработва същата задача с помощта на CTEs:
WITH OrdCount AS( SELECT YEAR(orderdate) AS order year, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate))SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diffFROM OrdCount AS CUR LEFT OUTER JOIN OrdCount AS PRV ON CUR.orderyear =PRV.orderyear + 1;Тук има ясно предимство; дефинирате само един израз на именувана таблица въз основа на един екземпляр на вътрешната заявка и се препращате към него два пъти от външната заявка.
В този смисъл възгледите са по-подобни на CTE. Дефинирате само един изглед въз основа само на едно копие на заявката, така:
СЪЗДАВАЙТЕ ИЛИ ПРОМЕНЯТЕ ПРЕГЛЕД Sales.YearlyOrderCountsAS ИЗБЕРЕТЕ ГОДИНА(дата на поръчка) КАТО година на поръчка, COUNT(*) КАТО номера на поръчки ОТ Sales.Поръчки ГРУПА ПО ГОДИНА(дата на поръчка);GOНо по-добре, отколкото при CTE, не сте ограничени до повторно използване на израза на именувана таблица само във външния израз. Можете да използвате повторно името на изгледа произволен брой пъти, с произволен брой несвързани заявки, стига да имате правилните разрешения. Ето кода за постигане на задачата чрез използване на множество препратки към изгледа:
ИЗБЕРЕТЕ CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders КАТО diffFROM Sales.YearlyOrderCounts AS CUR LEFT OUTER JOIN Sales.YearlyOrderCounts КАТО PRV ON CUR.orderyear =PRV.orderyear + 1;Изглежда, че изгледите са по-подобни на CTE, отколкото на извлечени таблици, с допълнителната функционалност като инструмент за многократна употреба, с възможност за контрол на разрешенията. Или за да го обърнем, вероятно е подходящо да мислим за CTE като изглед с обхват на изявление. Сега това, което би могло да бъде наистина прекрасно, е, ако имахме и израз на именувана таблица с по-широк обхват от този на CTE, по-тесен от този на изглед. Например, нямаше ли да е чудесно, ако имахме табличен израз с обхват на ниво сесия?
Резюме
Обичам тази тема. Има толкова много в табличните изрази, които се коренят в релационната теория, която от своя страна се корени в математиката. Обичам да знам кои са правилните термини за нещата и като цяло се уверявам, че основите ми са измислени внимателно, дори ако на някои може да изглежда като придирчив и прекалено педантичен. Поглеждайки назад към моя учебен процес през годините, виждам много ясен път между настояването за добро разбиране на основите, използването на правилна терминология и наистина познаването на нещата по-късно, когато се стигне до много по-напреднали и сложни неща.
И така, кои са критичните части, когато става дума за изгледи?
- Изгледът е таблица.
- Това е таблица, която е извлечена от заявка (израз на таблица).
- Дава му се име, което на потребителя изглежда като име на таблица, тъй като е име на таблица.
- Създава се като постоянен обект в базата данни.
- Можете да контролирате разрешенията за достъп спрямо изгледа.
Изгледите са подобни на CTE по редица начини. В смисъл, че развивате решенията си по модулен начин, като се фокусирате върху една единица в даден момент от началото до края. Също така в смисъл, че можете да имате множество препратки към името на изгледа от външната заявка. Но по-добре от CTE, изгледите не са ограничени само до обхвата на външния израз, а могат да се използват повторно, докато не бъдат изхвърлени от базата данни.
Има още много какво да се каже за изгледите и ще продължа дискусията следващия месец. Междувременно искам да ви оставя с една мисъл. С извлечени таблици и CTE можете да направите аргумент в полза на SELECT * във вътрешна заявка. Вижте случая, който направих за него в част 3 от поредицата за подробности. Можете ли да направите подобен случай с изгледите или е лоша идея с тях?