Questa è la nona parte di una serie sulle espressioni di tabelle con nome. Nella parte 1 ho fornito lo sfondo alle espressioni di tabelle con nome, che includono tabelle derivate, espressioni di tabelle comuni (CTE), viste e funzioni con valori di tabelle inline (iTVF). Nella parte 2, parte 3 e parte 4 mi sono concentrato sulle tabelle derivate. Nella parte 5, parte 6, parte 7 e parte 8 mi sono concentrato sui CTE. Come ho spiegato, le tabelle derivate e le CTE sono espressioni di tabelle denominate con ambito istruzione. Una volta terminata la dichiarazione che li definisce, non ci sono più.
Siamo ora pronti per procedere alla copertura delle espressioni di tabelle con nome riutilizzabili. Cioè, quelli che vengono creati come oggetti nel database e rimangono lì permanentemente a meno che non vengano eliminati. In quanto tali, sono accessibili e riutilizzabili da tutti coloro che dispongono delle autorizzazioni corrette. Visualizzazioni e iTVF rientrano in questa categoria. La differenza tra i due è principalmente che il primo non supporta i parametri di input e il secondo sì.
In questo articolo inizio la copertura delle visualizzazioni. Come ho fatto prima, mi concentrerò prima sugli aspetti logici o concettuali e in un secondo momento procederò agli aspetti di ottimizzazione. Con il primo articolo sulle viste, voglio iniziare leggero, concentrandomi su cosa sia una vista, usando la terminologia corretta e confrontare le considerazioni di progettazione delle viste con quelle delle tabelle derivate e dei CTE precedentemente discussi.
Nei miei esempi userò un database di esempio chiamato TSQLV5. Puoi trovare lo script che lo crea e lo popola qui e il suo diagramma ER qui.
Cos'è una vista?
Come al solito quando discutiamo di teoria relazionale, a noi professionisti SQL viene spesso detto che la terminologia che stiamo usando è sbagliata. Quindi, con questo spirito, fin dall'inizio, inizierò dicendo che quando usi il termine tabelle e viste , è sbagliato. L'ho imparato da Chris Date.
Ricordiamo che una tabella è la controparte SQL di una relazione (semplificando un po' troppo la discussione su valori e variabili). Una tabella potrebbe essere una tabella di base definita come un oggetto nel database, oppure potrebbe essere una tabella restituita da un'espressione, in particolare un'espressione di tabella. È simile al fatto che una relazione potrebbe essere quella restituita da un'espressione relazionale. Un'espressione di tabella potrebbe essere una query.
Ora, cos'è una vista? È un'espressione di tabella denominata, proprio come un CTE è un'espressione di tabella denominata. È solo che, come ho detto, una vista è un'espressione di tabella denominata riutilizzabile che viene creata come oggetto nel database ed è accessibile a coloro che dispongono delle autorizzazioni corrette. Questo è tutto per dire, una vista è una tabella. Non è un tavolo basso, ma comunque un tavolo. Quindi, proprio come dire "un rettangolo e un quadrato" o "un whisky e un Lagavulin" sembrerebbe strano (a meno che tu non abbia troppo Lagavulin!), usare "tabelle e viste" è altrettanto improprio.
Sintassi
Ecco la sintassi T-SQL per un'istruzione CREATE VIEW:
CREATE [O ALTER] VISUALIZZA [[ WITH
AS
[ WITH CHECK OPTION ]
[; ]
L'istruzione CREATE VIEW deve essere la prima e l'unica istruzione nel batch.
Si noti che la parte CREATE OR ALTER è stata introdotta in SQL Server 2016 SP1, quindi se si utilizza una versione precedente, sarà necessario lavorare con istruzioni CREATE VIEW e ALTER VIEW separate a seconda che l'oggetto esista già o meno. Come probabilmente ben saprai, la modifica di un oggetto esistente conserva le autorizzazioni assegnate. Questo è uno dei motivi per cui di solito è sensato alterare un oggetto esistente invece di lasciarlo cadere e ricrearlo. Ciò che sorprende alcune persone è che la modifica di una vista non mantiene gli attributi di visualizzazione esistenti; quelli devono essere ridefiniti se vuoi mantenerli.
Ecco un esempio per una semplice definizione di vista che rappresenta i clienti USA:
USE TSQLV5; GO CREATE OR ALTER VIEW Sales.USACustomers AS SELECT custid, companyname FROM Sales.Customers WHERE country = N'USA'; GO
Ed ecco una dichiarazione che interroga la vista:
SELECT custid, companyname FROM Sales.USACustomers;
Tra l'istruzione che crea la vista e l'istruzione che la interroga, troverai gli stessi tre elementi coinvolti in un'istruzione rispetto a una tabella derivata o a un CTE:
- L'espressione della tabella interna (la query interna della vista)
- Il nome della tabella assegnata (il nome della vista)
- L'istruzione con la query esterna rispetto alla vista
Quelli di voi con un occhio attento avranno notato che in realtà ci sono due espressioni da tavolo coinvolte qui. C'è quello interno (la query interna della vista) e c'è quello esterno (la query nella dichiarazione contro la vista). Nell'istruzione con la query sulla vista, la query stessa è un'espressione di tabella e, una volta aggiunto il terminatore, diventa un'istruzione. Potrebbe sembrare schizzinoso, ma se lo ottieni e chiami le cose con il nome giusto, si riflette sulla tua conoscenza. E non è fantastico quando sai di sapere?
Inoltre, tutti i requisiti dell'espressione tabella nelle tabelle derivate e nei CTE discussi in precedenza nella serie si applicano all'espressione tabella su cui si basa la vista. Ricordiamo che i requisiti sono:
- Tutte le colonne dell'espressione della tabella devono avere nomi
- Tutti i nomi delle colonne dell'espressione della tabella devono essere univoci
- Le righe dell'espressione della tabella non hanno ordine
Se hai bisogno di rinfrescare la tua comprensione di cosa c'è dietro questi requisiti, consulta la sezione "Un'espressione di tabella è una tabella" nella Parte 2 della serie. Assicurati di aver compreso in particolare la parte "nessun ordine". Come breve promemoria, un'espressione di tabella è una tabella e come tale non ha ordine. Ecco perché non è possibile creare una vista basata su una query con una clausola ORDER BY, a meno che questa clausola non supporti un filtro TOP o OFFSET-FETCH. E anche con questa eccezione che consente alla query interna di avere una clausola ORDER BY, si desidera ricordare che se la query esterna rispetto alla vista non ha la propria clausola ORDER BY, non si ottiene la garanzia che la query verrà restituita le righe in un ordine particolare, non importa il comportamento osservato. Questo è molto importante da capire!
Nidificazione e riferimenti multipli
Quando si discutono le considerazioni sulla progettazione di tabelle derivate e CTE, ho confrontato i due in termini sia di annidamento che di riferimenti multipli. Ora vediamo come vanno le visualizzazioni in questi dipartimenti. Inizierò con la nidificazione. A tal fine, confronteremo il codice che restituisce gli anni in cui più di 70 clienti hanno effettuato ordini utilizzando tabelle, CTE e viste derivate. Hai già visto il codice con tabelle derivate e CTE in precedenza nella serie. Ecco il codice che gestisce l'attività utilizzando le tabelle derivate:
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;
Ho sottolineato che il principale svantaggio che vedo con le tabelle derivate qui è il fatto che si annidano le definizioni di tabelle derivate e questo può portare a complessità nella comprensione, manutenzione e risoluzione dei problemi di tale codice.
Ecco il codice che gestisce la stessa attività utilizzando le 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;
Ho sottolineato che per me questo sembra un codice molto più chiaro a causa della mancanza di annidamento. Puoi vedere ogni passaggio della soluzione dall'inizio alla fine separatamente nella propria unità, con la logica della soluzione che scorre chiaramente dall'alto verso il basso. Pertanto, vedo l'opzione CTE come un miglioramento rispetto alle tabelle derivate in questo senso.
Ora alle visualizzazioni. Ricorda, uno dei principali vantaggi delle viste è la riutilizzabilità. Puoi anche controllare i permessi di accesso. Lo sviluppo delle unità coinvolte è un po' più simile ai CTE, nel senso che puoi concentrare la tua attenzione su un'unità alla volta dall'inizio alla fine. Inoltre, hai la flessibilità di decidere se creare una vista separata per unità nella soluzione, o forse solo una vista basata su una query che coinvolge espressioni di tabelle con nome con ambito istruzione.
Andresti con il primo quando ciascuna delle unità deve essere riutilizzabile. Ecco il codice che useresti in questo caso, creando tre viste:
-- 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
Puoi eseguire query su ciascuna vista in modo indipendente, ma ecco il codice che utilizzeresti per restituire ciò che l'attività originale cercava.
SELECT orderyear, numcusts FROM Sales.YearlyCustCountsAbove70;
Se esiste un requisito di riutilizzabilità solo per la parte più esterna (ciò che richiedeva l'attività originale), non è necessario sviluppare tre diverse viste. È possibile creare una vista basata su una query che coinvolge CTE o tabelle derivate. Ecco come faresti con una query che coinvolge 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
A proposito, se non fosse ovvio, i CTE su cui si basa la query interna della vista possono essere ricorsivi.
Procediamo ai casi in cui sono necessari più riferimenti alla stessa espressione di tabella dalla query esterna. L'attività per questo esempio è calcolare il conteggio annuale degli ordini per anno e confrontare il conteggio di ogni anno con l'anno precedente. Il modo più semplice per ottenere questo risultato è in realtà utilizzare la funzione della finestra LAG, ma utilizzeremo un join tra due istanze di un'espressione di tabella che rappresenta i conteggi annuali degli ordini solo per confrontare un caso multi-riferimento tra i tre strumenti.
Questo è il codice che abbiamo usato in precedenza nella serie per gestire l'attività con le tabelle derivate:
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;
C'è un aspetto negativo molto chiaro qui. Devi ripetere la definizione dell'espressione della tabella due volte. Stai essenzialmente definendo due espressioni di tabella con nome basate sullo stesso codice di query.
Ecco il codice che gestisce la stessa attività utilizzando le 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;
C'è un chiaro vantaggio qui; definisci solo un'espressione di tabella denominata in base a una singola istanza della query interna e fai riferimento ad essa due volte dalla query esterna.
Le visualizzazioni sono più simili alle CTE in questo senso. Definisci solo una vista in base a una sola copia della query, in questo modo:
CREATE OR ALTER VIEW Sales.YearlyOrderCounts AS SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate); GO
Ma meglio che con i CTE, non sei limitato a riutilizzare l'espressione della tabella denominata solo nell'istruzione esterna. Puoi riutilizzare il nome della vista tutte le volte che vuoi, con qualsiasi numero di query non correlate, purché tu disponga delle autorizzazioni corrette. Ecco il codice per eseguire l'attività utilizzando più riferimenti alla vista:
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;
Sembra che le viste siano più simili alle CTE che alle tabelle derivate, con la funzionalità aggiuntiva di essere uno strumento più riutilizzabile, con la possibilità di controllare le autorizzazioni. O per ribaltare la situazione, è probabilmente appropriato pensare a un CTE come a una vista con ambito di affermazione. Ora, ciò che potrebbe essere davvero meraviglioso è se avessimo anche un'espressione di tabella denominata con un ambito più ampio di quello di un CTE, più ristretto di quello di una vista. Ad esempio, non sarebbe stato fantastico se avessimo avuto un'espressione di tabella denominata con ambito a livello di sessione?
Riepilogo
Amo questo argomento. C'è così tanto nelle espressioni tabellari che è radicato nella teoria relazionale, che a sua volta è radicata nella matematica. Mi piace sapere quali sono i termini giusti per le cose e, in generale, assicurarmi di aver individuato attentamente le basi, anche se ad alcuni potrebbe sembrare pignolo e troppo pedante. Guardando indietro al mio processo di apprendimento nel corso degli anni, posso vedere un percorso molto chiaro tra insistere su una buona conoscenza delle basi, usare una terminologia corretta e conoscere davvero le tue cose in seguito, quando si arriva a cose molto più avanzate e complesse.
Quindi, quali sono i pezzi critici quando si tratta di visualizzazioni?
- Una vista è una tabella.
- È una tabella derivata da una query (un'espressione di tabella).
- Viene assegnato un nome che all'utente appare come il nome di una tabella, poiché è un nome di tabella.
- Viene creato come oggetto permanente nel database.
- Puoi controllare i permessi di accesso rispetto alla vista.
Le visualizzazioni sono simili alle CTE in diversi modi. Nel senso che sviluppi le tue soluzioni in modo modulare, concentrandoti su un'unità alla volta dall'inizio alla fine. Anche nel senso che puoi avere più riferimenti al nome della vista dalla query esterna. Ma meglio dei CTE, le viste non si limitano solo all'ambito dell'istruzione esterna, ma sono riutilizzabili fino a quando non vengono eliminate dal database.
C'è molto altro da dire sulle visualizzazioni e continuerò la discussione il mese prossimo. Intanto vi voglio lasciare con un pensiero. Con le tabelle derivate e le CTE potresti fare un caso a favore di SELECT * in una query interna. Vedi il caso che ho realizzato nella parte 3 della serie per i dettagli. Potresti fare un caso simile con le visualizzazioni o è una cattiva idea con quelle?