Database
 sql >> Baza danych >  >> RDS >> Database

Podstawy wyrażeń tabelowych, Część 9 – Widoki w porównaniu z tabelami pochodnymi i CTE

Jest to dziewiąta część serii poświęconej nazwanym wyrażeniom tabelowym. W części 1 przedstawiłem tło nazwanych wyrażeń tabelarycznych, które obejmują tabele pochodne, wspólne wyrażenia tabelowe (CTE), widoki i wbudowane funkcje tabelaryczne (iTVF). W części 2, części 3 i części 4 skupiłem się na tabelach pochodnych. W części 5, części 6, części 7 i części 8 skupiłem się na CTE. Jak wyjaśniłem, tabele pochodne i CTE są nazwanymi wyrażeniami tabelowymi o zasięgu instrukcji. Gdy zakończy się stwierdzenie, które je definiuje, znikają.

Jesteśmy teraz gotowi do omówienia nazwanych wyrażeń tabelowych wielokrotnego użytku. To znaczy takie, które są tworzone jako obiekt w bazie danych i pozostają tam na stałe, chyba że zostaną usunięte. W związku z tym są dostępne i wielokrotnego użytku dla wszystkich, którzy mają odpowiednie uprawnienia. Do tej kategorii należą widoki i iTVF. Różnica między nimi polega przede wszystkim na tym, że pierwsza nie obsługuje parametrów wejściowych, a druga tak.

W tym artykule rozpoczynam omówienie poglądów. Tak jak wcześniej, najpierw skupię się na aspektach logicznych lub koncepcyjnych, a później przejdę do aspektów optymalizacji. W pierwszym artykule o widokach chcę zacząć od skupienia się na tym, czym jest widok, używając prawidłowej terminologii i porównać rozważania projektowe widoków z tymi z wcześniej omówionych tabel pochodnych i CTE.

W moich przykładach użyję przykładowej bazy danych o nazwie TSQLV5. Skrypt, który tworzy i wypełnia go tutaj, oraz jego diagram ER można znaleźć tutaj.

Co to jest widok?

Jak zwykle przy omawianiu teorii relacji, my, praktykujący SQL, często słyszymy, że terminologia, której używamy, jest błędna. Tak więc, w tym duchu, od razu zacznę od stwierdzenia, że ​​kiedy użyjesz terminu tabele i widoki , to jest źle. Nauczyłem się tego od Chrisa Date.

Przypomnijmy, że tabela jest odpowiednikiem relacji SQL w SQL (nieco upraszcza dyskusję na temat wartości i zmiennych). Tabela może być tabelą bazową zdefiniowaną jako obiekt w bazie danych lub może być tabelą zwróconą przez wyrażenie — a dokładniej wyrażenie tabelowe. Przypomina to fakt, że relacja może być relacją zwróconą z wyrażenia relacyjnego. Wyrażeniem tabelowym może być zapytanie.

Czym jest widok? Jest to nazwane wyrażenie tabeli, podobnie jak CTE jest nazwanym wyrażeniem tabeli. Po prostu, jak powiedziałem, widok jest nazwanym wyrażeniem tabelowym wielokrotnego użytku, które jest tworzone jako obiekt w bazie danych i jest dostępne dla tych, którzy mają odpowiednie uprawnienia. Można powiedzieć, że widok to stół. To nie jest stół podstawowy, ale mimo to stół. Więc tak jak powiedzenie „prostokąt i kwadrat” lub „whisky i lagavulin” wydawałoby się dziwne (chyba że miałeś za dużo Lagavulinu!), użycie „tabel i widoków” jest równie niewłaściwe.

Składnia

Oto składnia T-SQL dla instrukcji CREATE VIEW:

CREATE [ OR ALTER ] VIEW [ . ] [ () ]
[ Z ]
AS

[ Z OPCJI SPRAWDZANIA ]
[; ]

Instrukcja CREATE VIEW musi być pierwszą i jedyną instrukcją w partii.

Zauważ, że część CREATE OR ALTER została wprowadzona w SQL Server 2016 SP1, więc jeśli używasz wcześniejszej wersji, będziesz musiał pracować z oddzielnymi instrukcjami CREATE VIEW i ALTER VIEW, w zależności od tego, czy obiekt już istnieje, czy nie. Jak zapewne dobrze wiesz, zmiana istniejącego obiektu zachowuje przypisane uprawnienia. To jeden z powodów, dla których zwykle rozsądnie jest zmienić istniejący obiekt, zamiast go upuszczać i odtwarzać. To, co zaskakuje niektórych, to fakt, że zmiana widoku nie zachowuje istniejących atrybutów widoku; należy je ponownie określić, jeśli chcesz je zachować.

Oto przykład prostej definicji widoku reprezentującej klientów z USA:

USE TSQLV5;
GO
 
CREATE OR ALTER VIEW Sales.USACustomers
AS
  SELECT custid, companyname
  FROM Sales.Customers
  WHERE country = N'USA';
GO

A oto stwierdzenie, które pyta o widok:

SELECT custid, companyname
FROM Sales.USACustomers;

Pomiędzy instrukcją, która tworzy widok, a instrukcją, która go odpytuje, znajdziesz te same trzy elementy, które występują w instrukcji przeciwko tabeli pochodnej lub CTE:

  1. Wewnętrzne wyrażenie tabeli (wewnętrzne zapytanie widoku)
  2. Przypisana nazwa tabeli (nazwa widoku)
  3. Oświadczenie z zewnętrznym zapytaniem względem widoku

Ci z was, którzy mają bystre oko, zauważą, że w grę wchodzą w rzeczywistości dwa wyrażenia przy stole. Jest to wewnętrzne (zapytanie wewnętrzne widoku) i jest zewnętrzne (zapytanie w wyrażeniu przeciwko widokowi). W instrukcji zawierającej zapytanie względem widoku samo zapytanie jest wyrażeniem tabelowym, a po dodaniu terminatora staje się instrukcją. Może to zabrzmieć wybrednie, ale jeśli to zrozumiesz i nazwiesz rzeczy po imieniu, odzwierciedla to twoją wiedzę. I czy nie jest wspaniale, gdy wiesz, że wiesz?

Ponadto wszystkie wymagania z wyrażenia tabeli w tabelach pochodnych i CTE, które omówiliśmy wcześniej w tej serii, mają zastosowanie do wyrażenia tabeli, na którym oparty jest widok. Przypominamy, że wymagania to:

  • Wszystkie kolumny wyrażenia tabelowego muszą mieć nazwy
  • Wszystkie nazwy kolumn wyrażenia tabeli muszą być unikalne
  • Wiersze wyrażenia tabeli nie mają kolejności

Jeśli chcesz odświeżyć swoje zrozumienie tego, co kryje się za tymi wymaganiami, zobacz sekcję „Wyrażenie tabelowe to tabela” w części 2 serii. Upewnij się, że szczególnie rozumiesz część „bez zamówienia”. Krótko mówiąc, wyrażenie tabelowe jest tabelą i jako takie nie ma kolejności. Dlatego nie można utworzyć widoku opartego na zapytaniu z klauzulą ​​ORDER BY, chyba że ta klauzula ma obsługiwać filtr TOP lub OFFSET-FETCH. I nawet z tym wyjątkiem, że zapytanie wewnętrzne może mieć klauzulę ORDER BY, chcesz pamiętać, że jeśli zapytanie zewnętrzne względem widoku nie ma własnej klauzuli ORDER BY, nie masz gwarancji, że zapytanie zwróci wiersze w dowolnej kolejności, nieważne obserwowane zachowanie. Bardzo ważne jest, aby to zrozumieć!

Zagnieżdżanie i wiele odwołań

Omawiając zagadnienia dotyczące projektowania tabel pochodnych i CTE, porównałem je pod względem zarówno zagnieżdżenia, jak i wielu odwołań. Zobaczmy teraz, jak prezentują się widoki w tych działach. Zacznę od zagnieżdżania. W tym celu porównamy kod zwracający lata, w których ponad 70 klientów złożyło zamówienia przy użyciu tabel pochodnych, CTE i widoków. Widziałeś już kod z tabelami pochodnymi i CTE we wcześniejszej części serii. Oto kod, który obsługuje zadanie przy użyciu tabel pochodnych:

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;

Wskazałem, że główną wadą, którą widzę w przypadku tabel pochodnych, jest to, że zagnieżdżasz definicje tabel pochodnych, co może prowadzić do złożoności zrozumienia, utrzymania i rozwiązywania problemów z takim kodem.

Oto kod, który obsługuje to samo zadanie przy użyciu CTE:

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;

Zwróciłem uwagę, że wydaje mi się, że kod jest znacznie wyraźniejszy z powodu braku zagnieżdżenia. Możesz zobaczyć każdy krok w rozwiązaniu od początku do końca osobno w osobnej jednostce, z logiką rozwiązania wyraźnie płynącą od góry do dołu. Dlatego uważam, że opcja CTE jest pod tym względem ulepszeniem w stosunku do tabel pochodnych.

Teraz do widoków. Pamiętaj, że jedną z głównych zalet widoków jest możliwość ponownego wykorzystania. Możesz także kontrolować uprawnienia dostępu. Rozwój zaangażowanych jednostek jest nieco bardziej podobny do CTE w tym sensie, że możesz skupić swoją uwagę na jednej jednostce na raz od początku do końca. Co więcej, masz swobodę decydowania, czy utworzyć oddzielny widok na jednostkę w rozwiązaniu, czy może tylko jeden widok oparty na zapytaniu obejmującym nazwane wyrażenia tabelowe o zasięgu instrukcji.

Poszedłbyś z tym pierwszym, gdy każda z jednostek musi być wielokrotnego użytku. Oto kod, którego użyjesz w takim przypadku, tworząc trzy widoki:

-- 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

Możesz wysyłać zapytania do każdego z widoków niezależnie, ale oto kod, którego użyjesz do zwrócenia tego, co było po pierwotnym zadaniu.

SELECT orderyear, numcusts
FROM Sales.YearlyCustCountsAbove70;

Jeśli istnieje wymóg ponownego użycia tylko dla najbardziej zewnętrznej części (co wymagało pierwotnego zadania), nie ma rzeczywistej potrzeby opracowywania trzech różnych widoków. Można utworzyć jeden widok na podstawie zapytania obejmującego CTE lub tabele pochodne. Oto, jak można to zrobić z zapytaniem dotyczącym CTE:

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

Nawiasem mówiąc, jeśli nie było to oczywiste, CTE, na którym opiera się wewnętrzne zapytanie widoku, mogą być rekurencyjne.

Przejdźmy do przypadków, w których potrzebujesz wielu odwołań do tego samego wyrażenia tabelowego z zapytania zewnętrznego. Zadaniem tego przykładu jest obliczenie rocznej liczby zamówień na rok i porównanie liczby w każdym roku z poprzednim rokiem. Najłatwiejszym sposobem na osiągnięcie tego jest użycie funkcji okna LAG, ale użyjemy sprzężenia między dwoma wystąpieniami wyrażenia tabelowego reprezentującego roczne liczby zamówień tylko po to, aby porównać przypadek z wieloma referencjami wśród trzech narzędzi.

Oto kod, którego używaliśmy wcześniej w tej serii do obsługi zadania z tabelami pochodnymi:

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;

Tutaj jest bardzo wyraźny minus. Musisz dwukrotnie powtórzyć definicję wyrażenia tabelowego. Zasadniczo definiujesz dwa nazwane wyrażenia tabelowe oparte na tym samym kodzie zapytania.

Oto kod, który obsługuje to samo zadanie przy użyciu CTE:

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;

Tutaj jest wyraźna przewaga; definiujesz tylko jedno nazwane wyrażenie tabelowe oparte na pojedynczym wystąpieniu zapytania wewnętrznego i odwołujesz się do niego dwukrotnie z zapytania zewnętrznego.

W tym sensie poglądy są bardziej zbliżone do CTE. Definiujesz tylko jeden widok na podstawie tylko jednej kopii zapytania, na przykład:

CREATE OR ALTER VIEW Sales.YearlyOrderCounts
AS
  SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
  FROM Sales.Orders
  GROUP BY YEAR(orderdate);
GO

Ale lepiej niż w przypadku CTE, nie ograniczasz się do ponownego użycia nazwanego wyrażenia tabeli tylko w zewnętrznej instrukcji. Możesz użyć nazwy widoku dowolną liczbę razy, z dowolną liczbą niepowiązanych zapytań, o ile masz odpowiednie uprawnienia. Oto kod umożliwiający wykonanie zadania przy użyciu wielu odniesień do widoku:

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;

Wygląda na to, że widoki są bardziej podobne do CTE niż do tabel pochodnych, z dodatkową funkcjonalnością, jaką jest narzędzie do wielokrotnego użytku, z możliwością kontrolowania uprawnień. Lub, aby to odwrócić, prawdopodobnie należy pomyśleć o CTE jako widoku o zakresie instrukcji. Teraz, co mogłoby być naprawdę cudowne, byłoby gdybyśmy mieli również nazwane wyrażenie tabelowe o szerszym zakresie niż CTE, węższym niż widok. Na przykład, czy nie byłoby wspaniale, gdybyśmy mieli nazwane wyrażenie tabelowe o zasięgu na poziomie sesji?

Podsumowanie

Uwielbiam ten temat. Jest tak wiele wyrażeń tabelarycznych, które są zakorzenione w teorii relacji, która z kolei jest zakorzeniona w matematyce. Uwielbiam wiedzieć, jakie są właściwe terminy i generalnie upewniam się, że mam starannie opracowane podstawy, nawet jeśli niektórym może się to wydawać wybredne i zbyt pedantyczne. Patrząc wstecz na mój proces uczenia się na przestrzeni lat, widzę bardzo wyraźną ścieżkę między naleganiem na dobre zrozumienie podstaw, używanie prawidłowej terminologii, a naprawdę poznawaniem swoich rzeczy później, gdy dojdzie do znacznie bardziej zaawansowanych i złożonych rzeczy.

Jakie są więc najważniejsze elementy, jeśli chodzi o wyświetlenia?

  • Widok to tabela.
  • Jest to tabela wywodząca się z zapytania (wyrażenia tabeli).
  • Otrzymuje nazwę, która dla użytkownika wygląda jak nazwa tabeli, ponieważ jest to nazwa tabeli.
  • Jest tworzony jako stały obiekt w bazie danych.
  • Możesz kontrolować uprawnienia dostępu do widoku.

Widoki są pod wieloma względami podobne do CTE. W tym sensie, że rozwijasz swoje rozwiązania w sposób modułowy, skupiając się na jednej jednostce na raz, od początku do końca. Również w tym sensie, że możesz mieć wiele odwołań do nazwy widoku z zewnętrznego zapytania. Ale lepiej niż CTE, widoki nie ograniczają się tylko do zakresu zewnętrznej instrukcji, ale można ich używać wielokrotnie, dopóki nie zostaną usunięte z bazy danych.

O poglądach można powiedzieć o wiele więcej, a dyskusję będę kontynuował w przyszłym miesiącu. W międzyczasie chcę zostawić cię z myślą. W przypadku tabel pochodnych i CTE można postawić argument na korzyść SELECT * w zapytaniu wewnętrznym. Zobacz przypadek, który wykonałem w części 3 serii, aby uzyskać szczegółowe informacje. Czy możesz zrobić podobną sprawę z poglądami, czy może to zły pomysł z nimi?


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Podłączanie MS SQL do IRI Workbench

  2. Raport dotyczący bazy danych Open Source 2019:najlepsze bazy danych, chmura publiczna a lokalna, trwałość Polyglot

  3. Korzystanie ze śledzenia przyczynowości w celu zrozumienia wykonywania zapytania

  4. Łączenie systemów Linux i UNIX z usługą Azure SQL Data Warehouse

  5. Nowe standardowe rozmiary warstw bazy danych SQL Azure