Dies ist der 9. Teil einer Serie über benannte Tabellenausdrücke. In Teil 1 habe ich den Hintergrund zu benannten Tabellenausdrücken bereitgestellt, zu denen abgeleitete Tabellen, allgemeine Tabellenausdrücke (CTEs), Ansichten und Inline-Tabellenwertfunktionen (iTVFs) gehören. In Teil 2, Teil 3 und Teil 4 habe ich mich auf abgeleitete Tabellen konzentriert. In Teil 5, Teil 6, Teil 7 und Teil 8 habe ich mich auf CTEs konzentriert. Wie ich bereits erklärt habe, sind abgeleitete Tabellen und CTEs benannte Tabellenausdrücke im Geltungsbereich von Anweisungen. Sobald die Anweisung, die sie definiert, beendet ist, sind sie weg.
Wir sind jetzt bereit, mit der Abdeckung wiederverwendbarer benannter Tabellenausdrücke fortzufahren. Das heißt, diejenigen, die als Objekt in der Datenbank erstellt werden und dort dauerhaft verbleiben, sofern sie nicht gelöscht werden. Als solche sind sie für alle zugänglich und wiederverwendbar, die über die entsprechenden Berechtigungen verfügen. Views und iTVFs fallen in diese Kategorie. Der Unterschied zwischen den beiden besteht hauptsächlich darin, dass Ersteres keine Eingabeparameter unterstützt und Letzteres schon.
In diesem Artikel beginne ich mit der Berichterstattung über Ansichten. Wie ich es zuvor getan habe, werde ich mich zunächst auf logische oder konzeptionelle Aspekte konzentrieren und später zu Optimierungsaspekten übergehen. Mit dem ersten Artikel über Ansichten möchte ich leicht beginnen, mich darauf konzentrieren, was eine Ansicht ist, korrekte Terminologie verwenden und Entwurfsüberlegungen zu Ansichten mit denen der zuvor besprochenen abgeleiteten Tabellen und CTEs vergleichen.
In meinen Beispielen verwende ich eine Beispieldatenbank namens TSQLV5. Sie finden das Skript, das es erstellt und füllt, hier und sein ER-Diagramm hier.
Was ist eine Ansicht?
Wie üblich, wenn wir über relationale Theorie diskutieren, wird uns SQL-Praktizierenden oft gesagt, dass die von uns verwendete Terminologie falsch ist. In diesem Sinne sage ich gleich zu Beginn, wenn Sie den Begriff Tabellen und Ansichten verwenden , es ist falsch. Das habe ich von Chris Date gelernt.
Erinnern Sie sich daran, dass eine Tabelle das SQL-Gegenstück zu einer Relation ist (um die Diskussion um Werte und Variablen ein wenig zu vereinfachen). Eine Tabelle kann eine Basistabelle sein, die als Objekt in der Datenbank definiert ist, oder eine Tabelle, die von einem Ausdruck zurückgegeben wird – genauer gesagt ein Tabellenausdruck. Das ist vergleichbar mit der Tatsache, dass eine Relation eine Relation sein könnte, die von einem relationalen Ausdruck zurückgegeben wird. Ein Tabellenausdruck könnte eine Abfrage sein.
Was ist nun eine Ansicht? Es ist ein benannter Tabellenausdruck, ähnlich wie ein CTE ein benannter Tabellenausdruck ist. Wie ich bereits sagte, ist eine Ansicht ein wiederverwendbarer benannter Tabellenausdruck, der als Objekt in der Datenbank erstellt wird und für diejenigen zugänglich ist, die über die entsprechenden Berechtigungen verfügen. Dies ist alles, um zu sagen, dass eine Ansicht eine Tabelle ist. Es ist kein Basistisch, aber dennoch ein Tisch. So wie es seltsam erscheinen würde, „ein Rechteck und ein Quadrat“ oder „ein Whisky und ein Lagavulin“ zu sagen (es sei denn, Sie hatten zu viel Lagavulin!), ist die Verwendung von „Tabellen und Ansichten“ ebenso unangemessen.
Syntax
Hier ist die T-SQL-Syntax für eine CREATE VIEW-Anweisung:
CREATE [ OR ALTER ] VIEW [[ WITH
AS
[ WITH CHECK OPTION ]
[; ]
Die CREATE VIEW-Anweisung muss die erste und einzige Anweisung im Stapel sein.
Beachten Sie, dass der CREATE OR ALTER-Teil in SQL Server 2016 SP1 eingeführt wurde. Wenn Sie also eine frühere Version verwenden, müssen Sie mit separaten CREATE VIEW- und ALTER VIEW-Anweisungen arbeiten, je nachdem, ob das Objekt bereits vorhanden ist oder nicht. Wie Sie wahrscheinlich wissen, bleiben beim Ändern eines vorhandenen Objekts zugewiesene Berechtigungen erhalten. Das ist einer der Gründe, warum es normalerweise sinnvoll ist, ein vorhandenes Objekt zu ändern, anstatt es zu löschen und neu zu erstellen. Was manche Leute überrascht, ist, dass beim Ändern einer Ansicht die vorhandenen Ansichtsattribute nicht beibehalten werden. diese müssen neu angegeben werden, wenn Sie sie beibehalten möchten.
Hier ist ein Beispiel für eine einfache Ansichtsdefinition, die Kunden in den USA repräsentiert:
USE TSQLV5; GO CREATE OR ALTER VIEW Sales.USACustomers AS SELECT custid, companyname FROM Sales.Customers WHERE country = N'USA'; GO
Und hier ist eine Anweisung, die die Ansicht abfragt:
SELECT custid, companyname FROM Sales.USACustomers;
Zwischen der Anweisung, die die Ansicht erstellt, und der Anweisung, die sie abfragt, finden Sie dieselben drei Elemente, die an einer Anweisung für eine abgeleitete Tabelle oder einen CTE beteiligt sind:
- Der innere Tabellenausdruck (die innere Abfrage der Ansicht)
- Der zugewiesene Tabellenname (der Ansichtsname)
- Die Anweisung mit der äußeren Abfrage gegen die Ansicht
Diejenigen unter Ihnen mit einem scharfen Auge werden bemerkt haben, dass es sich hier eigentlich um zwei Tabellenausdrücke handelt. Es gibt die innere (die innere Abfrage der Ansicht) und die äußere (die Abfrage in der Anweisung gegen die Ansicht). In der Anweisung mit der Abfrage für die Ansicht ist die Abfrage selbst ein Tabellenausdruck, und sobald Sie das Abschlusszeichen hinzufügen, wird sie zu einer Anweisung. Das mag wählerisch klingen, aber wenn Sie das verstehen und die Dinge beim richtigen Namen nennen, spiegelt es Ihr Wissen wider. Und ist es nicht toll, wenn man weiß, dass man es weiß?
Außerdem gelten alle Anforderungen aus dem Tabellenausdruck in abgeleiteten Tabellen und CTEs, die wir weiter oben in der Serie besprochen haben, für den Tabellenausdruck, auf dem die Ansicht basiert. Zur Erinnerung:Die Anforderungen sind:
- Alle Spalten des Tabellenausdrucks müssen Namen haben
- Alle Spaltennamen des Tabellenausdrucks müssen eindeutig sein
- Die Zeilen des Tabellenausdrucks haben keine Reihenfolge
Wenn Sie Ihr Verständnis der Hintergründe dieser Anforderungen auffrischen möchten, lesen Sie den Abschnitt „Ein Tabellenausdruck ist eine Tabelle“ in Teil 2 der Serie. Stellen Sie sicher, dass Sie insbesondere den Teil „keine Bestellung“ verstehen. Zur Erinnerung:Ein Tabellenausdruck ist eine Tabelle und hat als solche keine Ordnung. Aus diesem Grund können Sie keine Ansicht basierend auf einer Abfrage mit einer ORDER BY-Klausel erstellen, es sei denn, diese Klausel unterstützt einen TOP- oder OFFSET-FETCH-Filter. Und selbst mit dieser Ausnahme, die es der inneren Abfrage ermöglicht, eine ORDER BY-Klausel zu haben, sollten Sie daran denken, dass Sie keine Garantie dafür erhalten, dass die Abfrage zurückgegeben wird, wenn die äußere Abfrage für die Ansicht keine eigene ORDER BY-Klausel hat die Zeilen in einer bestimmten Reihenfolge, ganz zu schweigen vom beobachteten Verhalten. Das ist sehr wichtig zu verstehen!
Verschachtelung und Mehrfachreferenzen
Bei der Erörterung von Entwurfsüberlegungen zu abgeleiteten Tabellen und CTEs habe ich die beiden in Bezug auf Verschachtelung und Mehrfachreferenzen verglichen. Sehen wir uns nun an, wie die Aufrufe in diesen Abteilungen abschneiden. Ich beginne mit dem Verschachteln. Zu diesem Zweck vergleichen wir Code, der Jahre zurückgibt, in denen mehr als 70 Kunden Bestellungen mit abgeleiteten Tabellen, CTEs und Ansichten aufgegeben haben. Sie haben den Code mit abgeleiteten Tabellen und CTEs bereits früher in der Serie gesehen. Hier ist der Code, der die Aufgabe mit abgeleiteten Tabellen handhabt:
SELECT orderyear, numcusts FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) AS D1 GROUP BY orderyear ) AS D2 WHERE numcusts > 70;
Ich habe darauf hingewiesen, dass der Hauptnachteil, den ich hier bei abgeleiteten Tabellen sehe, die Tatsache ist, dass Sie abgeleitete Tabellendefinitionen verschachteln, und dies kann zu Komplexität beim Verstehen, Verwalten und Beheben von Fehlern in solchem Code führen.
Hier ist der Code, der die gleiche Aufgabe mit CTEs handhabt:
WITH C1 AS ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ), C2 AS ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C1 GROUP BY orderyear ) SELECT orderyear, numcusts FROM C2 WHERE numcusts > 70;
Ich habe darauf hingewiesen, dass sich dies für mich aufgrund der fehlenden Verschachtelung wie ein viel klarerer Code anfühlt. Sie können jeden Schritt in der Lösung von Anfang bis Ende separat in einer eigenen Einheit sehen, wobei die Logik der Lösung klar von oben nach unten fließt. Daher sehe ich die CTE-Option in dieser Hinsicht als Verbesserung gegenüber abgeleiteten Tabellen.
Nun zu Ansichten. Denken Sie daran, dass einer der Hauptvorteile von Ansichten die Wiederverwendbarkeit ist. Sie können auch Zugriffsberechtigungen steuern. Die Entwicklung der beteiligten Einheiten ähnelt CTEs insofern etwas mehr, als Sie Ihre Aufmerksamkeit von Anfang bis Ende auf jeweils eine Einheit richten können. Darüber hinaus haben Sie die Flexibilität, zu entscheiden, ob Sie eine separate Ansicht pro Einheit in der Lösung erstellen möchten oder vielleicht nur eine Ansicht basierend auf einer Abfrage, die benannte Tabellenausdrücke im Anweisungsbereich enthält.
Sie würden sich für Ersteres entscheiden, wenn jede der Einheiten wiederverwendbar sein muss. Hier ist der Code, den Sie in einem solchen Fall verwenden würden, um drei Ansichten zu erstellen:
-- Sales.OrderYears CREATE OR ALTER VIEW Sales.OrderYears AS SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders; GO -- Sales.YearlyCustCounts CREATE OR ALTER VIEW Sales.YearlyCustCounts AS SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM Sales.OrderYears GROUP BY orderyear; GO -- Sales.YearlyCustCountsMin70 CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70 AS SELECT orderyear, numcusts FROM Sales.YearlyCustCounts WHERE numcusts > 70; GO
Sie können jede der Ansichten unabhängig voneinander abfragen, aber hier ist der Code, den Sie verwenden würden, um zurückzugeben, wonach die ursprüngliche Aufgabe bestand.
SELECT orderyear, numcusts FROM Sales.YearlyCustCountsAbove70;
Wenn es nur für den äußersten Teil (was die ursprüngliche Aufgabe erforderte) eine Wiederverwendbarkeitsanforderung gibt, besteht keine wirkliche Notwendigkeit, drei verschiedene Ansichten zu entwickeln. Sie könnten eine Ansicht basierend auf einer Abfrage mit CTEs oder abgeleiteten Tabellen erstellen. So würden Sie das mit einer Abfrage mit CTEs machen:
CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70 AS WITH C1 AS ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ), C2 AS ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C1 GROUP BY orderyear ) SELECT orderyear, numcusts FROM C2 WHERE numcusts > 70; GO
Übrigens, wenn es nicht offensichtlich war, können die CTEs, auf denen die innere Abfrage der Ansicht basiert, rekursiv sein.
Fahren wir mit Fällen fort, in denen Sie mehrere Verweise auf denselben Tabellenausdruck aus der äußeren Abfrage benötigen. Die Aufgabe für dieses Beispiel besteht darin, die jährliche Bestellanzahl pro Jahr zu berechnen und die Anzahl in jedem Jahr mit dem Vorjahr zu vergleichen. Der einfachste Weg, dies zu erreichen, ist die Verwendung der LAG-Fensterfunktion, aber wir verwenden einen Join zwischen zwei Instanzen eines Tabellenausdrucks, der die jährlichen Bestellzahlen darstellt, nur um einen Fall mit mehreren Referenzen zwischen den drei Tools zu vergleichen.
Dies ist der Code, den wir früher in der Serie verwendet haben, um die Aufgabe mit abgeleiteten Tabellen zu handhaben:
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS CUR LEFT OUTER JOIN ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS PRV ON CUR.orderyear = PRV.orderyear + 1;
Hier gibt es einen ganz klaren Nachteil. Die Definition des Tabellenausdrucks müssen Sie zweimal wiederholen. Sie definieren im Wesentlichen zwei benannte Tabellenausdrücke, die auf demselben Abfragecode basieren.
Hier ist der Code, der die gleiche Aufgabe mit CTEs handhabt:
WITH OrdCount AS ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM OrdCount AS CUR LEFT OUTER JOIN OrdCount AS PRV ON CUR.orderyear = PRV.orderyear + 1;
Hier gibt es einen klaren Vorteil; Sie definieren nur einen benannten Tabellenausdruck basierend auf einer einzelnen Instanz der inneren Abfrage und verweisen zweimal von der äußeren Abfrage darauf.
Views sind CTEs in diesem Sinne ähnlicher. Sie definieren nur eine Ansicht basierend auf nur einer Kopie der Abfrage, etwa so:
CREATE OR ALTER VIEW Sales.YearlyOrderCounts AS SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate); GO
Aber besser als bei CTEs sind Sie nicht darauf beschränkt, den benannten Tabellenausdruck nur in der äußeren Anweisung wiederzuverwenden. Sie können den Ansichtsnamen beliebig oft mit einer beliebigen Anzahl unabhängiger Abfragen wiederverwenden, solange Sie über die entsprechenden Berechtigungen verfügen. Hier ist der Code, um die Aufgabe zu erreichen, indem mehrere Verweise auf die Ansicht verwendet werden:
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM Sales.YearlyOrderCounts AS CUR LEFT OUTER JOIN Sales.YearlyOrderCounts AS PRV ON CUR.orderyear = PRV.orderyear + 1;
Es scheint, dass Views CTEs ähnlicher sind als abgeleiteten Tabellen, mit der zusätzlichen Funktionalität, ein wiederverwendbares Tool zu sein, mit der Möglichkeit, Berechtigungen zu steuern. Oder um es umzukehren, es ist wahrscheinlich angemessen, sich einen CTE als eine auf Aussagen bezogene Ansicht vorzustellen. Was wirklich wunderbar wäre, wäre, wenn wir auch einen benannten Tabellenausdruck mit einem größeren Geltungsbereich als dem eines CTE und einem engeren Bereich als dem einer Ansicht hätten. Wäre es zum Beispiel nicht großartig gewesen, wenn wir einen benannten Tabellenausdruck auf Sitzungsebene gehabt hätten?
Zusammenfassung
Ich liebe dieses Thema. Es gibt so viel in Tabellenausdrücken, das in der relationalen Theorie verwurzelt ist, die wiederum in der Mathematik verwurzelt ist. Ich liebe es zu wissen, was die richtigen Begriffe für Dinge sind, und im Allgemeinen darauf zu achten, dass ich die Grundlagen sorgfältig herausgefunden habe, auch wenn es für manche pingelig und überpedantisch erscheinen mag. Wenn ich auf meinen Lernprozess im Laufe der Jahre zurückblicke, sehe ich einen sehr klaren Weg zwischen dem Beharren auf einem guten Verständnis der Grundlagen, der Verwendung korrekter Terminologie und der späteren wirklichen Kenntnis Ihrer Sachen, wenn es um die weitaus fortgeschritteneren und komplexeren Sachen geht.
Was sind also die kritischen Teile, wenn es um Aufrufe geht?
- Eine Ansicht ist eine Tabelle.
- Es ist eine Tabelle, die von einer Abfrage (einem Tabellenausdruck) abgeleitet wird.
- Sie erhält einen Namen, der für den Benutzer wie ein Tabellenname aussieht, da es sich um einen Tabellennamen handelt.
- Es wird als permanentes Objekt in der Datenbank erstellt.
- Sie können Zugriffsberechtigungen für die Ansicht steuern.
Ansichten ähneln CTEs in vielerlei Hinsicht. In dem Sinne, dass Sie Ihre Lösungen modular entwickeln und sich von Anfang bis Ende auf eine Einheit nach der anderen konzentrieren. Auch in dem Sinne, dass Sie mehrere Verweise auf den Ansichtsnamen aus der äußeren Abfrage haben können. Aber besser als CTEs sind Ansichten nicht nur auf den Geltungsbereich der äußeren Anweisung beschränkt, sondern wiederverwendbar, bis sie aus der Datenbank gelöscht werden.
Es gibt noch viel mehr über Aufrufe zu sagen, und ich werde die Diskussion nächsten Monat fortsetzen. In der Zwischenzeit möchte ich Sie mit einem Gedanken verlassen. Mit abgeleiteten Tabellen und CTEs könnten Sie in einer inneren Abfrage für SELECT * sprechen. Einzelheiten finden Sie in dem Fall, den ich dafür in Teil 3 der Serie erstellt habe. Könnten Sie einen ähnlichen Fall mit Ansichten machen, oder ist es eine schlechte Idee mit diesen?