18th Jul 2022 Tempo di lettura: 9 minuti Come usare le subquery nelle dichiarazioni INSERT, UPDATE e DELETE Ignacio L. Bisso sql imparare sql sottoquery Indice Punto di partenza: Un rapido ripasso delle subquery Prima tappa: usare le sottoquery nelle dichiarazioni INSERT Seconda tappa: le sottoquery nelle dichiarazioni UPDATE Terza tappa: le subquery nelle dichiarazioni DELETE Arrivare alla fine del tour delle subquery Sapevate che le subquery possono essere utilizzate anche nelle istruzioni UPDATE, INSERT e DELETE? Le subquery in SQL sono come blocchi di costruzione che possono essere utilizzati in una serie di istruzioni, non solo in SELECT. Se le subquery sono utili in SELECT, leggete questo articolo per scoprire quanto possono essere utili in altre istruzioni. In poco tempo sarete in grado di creare query SQL molto più complesse e potenti! Se volete rinfrescare le vostre conoscenze sulle subquery, vi consiglio il corso interattivo SQL Practice Set interattivo. Contiene oltre 80 esercizi sulle subquery e su altre costruzioni impegnative di SELECT. Punto di partenza: Un rapido ripasso delle subquery Iniziamo con un breve promemoria su cosa sia una subquery. Poiché le subquery sono usate più spesso nelle istruzioni SELECT, esamineremo un esempio di semplice subquery in un'istruzione SELECT. Una subquery può essere definita come una query all'interno di un'altra query. Sebbene le subquery siano usate più spesso nella clausola WHERE delle istruzioni SELECT, possono essere usate in molte altre clausole, tra cui WHERE, FROM e HAVING. Vediamo il database che useremo come esempio. Immaginiamo di essere il proprietario di un'enoteca e di avere un semplice database con 3 tabelle per gestire le attività del negozio. La prima tabella è wineche contiene i prodotti venduti, con il nome, il prezzo, il numero di bottiglie in magazzino e così via per ogni vino. La seconda è orderche memorizza gli ordini ricevuti dai nostri clienti, compreso il nome del vino ordinato e la quantità ordinata, oltre ad altre informazioni. wine NameTypeStockPriceWineCellar BrilliantChardonnay100022SkyWine BleuBlendCabernet98018LeBleu CatedralMalbec10027SantoRojo SantiagoMalbec204024Wines of Chile West SideCabernet140034Napa Wines Oro RossoCabernet75031Italian Caves High CoastChardonnay256017De la Costa wines order Order_idDateClient_idWine_namequantity 1Jan 10 2020100Catedral50 2Feb 15 2020103Santiago230 3Mar 12 2020102West Side85 4Mar 30 2020100Oro Rosso150 5May 3 2020100Oro Rosso30 6Jun 28 2020103Santiago200 7Jun 28 2020102West Side150 Supponiamo di voler ottenere un elenco di vini per i quali non abbiamo mai ricevuto un ordine. La query avrà il seguente aspetto: SELECT name, FROM wine WHERE name NOT IN ( SELECT wine_name FROM order ) La sottoquery restituisce i nomi di tutti i vini per i quali abbiamo ricevuto ordini. Poi la query esterna, utilizzando l'operatore NOT IN, ottiene i nomi dei vini non inclusi in nessun ordine. L'SQL è tanto semplice quanto potente! Se volete ripassare alcuni concetti di subquery, vi suggerisco il corso SQL Basics dove troverete una sezione completa sulle subquery. Prima tappa: usare le sottoquery nelle dichiarazioni INSERT Utilizziamo ora una subquery in un'istruzione INSERT. Si tratta di un'operazione molto comune; l'idea è quella di inserire in una tabella l'insieme completo dei risultati di una subquery o di un'istruzione SELECT. A titolo di esempio, immaginiamo di voler creare le fatture per tutti gli ordini di vino ricevuti nel corso della giornata. Di seguito è riportata una vista parziale della nostra tabella invoice tabella: Invoice_idDateClient_idAmountDescriptionOrder_id 12020-01-10100$13550 bottles of Catedral1 22020-02-15103$5520230 bottles of Santiago2 32020-03-12102$289085 bottles of West Side3 42020-03-30100$4650150 bottles of Oro Rosso4 52020-05-03100$93030 bottles of Oro Rosso5 Facciamo finta che la data odierna sia il 28 giugno 2020 e che vogliamo inserire i record delle fatture associate agli ordini di oggi. Possiamo utilizzare la seguente SELECT per generare i dati delle fatture: SELECT o.date, o.client_id, o.quantity * w.price as amount, o.quantity || ’ bottles of ‘ || o.wine_name, o.order_id FROM order o JOIN wine w ON w.name = o.wine_name WHERE o.date = ‘2020-06-28’ Il risultato di questa query è riportato di seguito: DateClient_idAmountDescriptionOrder_id 2020-06-28103$4800200 bottles of Santiago6 2020-06-28102$5100150 bottles of West Side7 Questo è esattamente ciò che vogliamo inserire nella tabella invoice. Se aggiungiamo semplicemente una clausola INSERT prima della query, possiamo inserire il risultato della query nella tabella winecome si può vedere nell'esempio seguente: INSERT INTO invoice (date, client_id, amount, wine_name, order_id) SELECT o.date, o.client_id, o.quantity * w.price as amount, o.quantity || ’ bottles of ‘ || o.wine_name, o.order_id FROM order o JOIN wine w ON w.name = o.wine_name WHERE o.date = ‘2020-06-28’ Si noti quanto segue: È necessario nominare esplicitamente le colonne della tabella invoice in cui stiamo inserendo. Le colonne dell'elenco SELECT devono essere nello stesso ordine delle colonne della tabella. Abbiamo omesso la colonna invoice_id per consentire al database di scegliere il valore successivo utilizzando un generatore di sequenze predefinito. Dopo aver eseguito la clausola INSERT, la tabella invoice conterrà i nuovi record delle fatture per gli ordini di oggi. Lo si può vedere qui sotto, con i nuovi record in rosso: Invoice_idDateClient_idAmountDescriptionOrder_id 12020-01-10100$13550 bottles of Catedral1 22020-02-15103$5520230 bottles of Santiago2 32020-03-12102$289085 bottles of West Side3 42020-03-30100$4650150 bottles of Oro Rosso4 52020-05-03100$93030 bottles of Oro Rosso5 62020-06-28103$4800200 bottles of Santiago6 72020-06-28102$5100150 bottles of West Side7 Supponiamo che in un determinato giorno abbiamo accidentalmente eseguito due volte il programma INSERT e che i nostri clienti abbiano ricevuto due fatture per ogni ordine. Non vogliamo ripetere lo stesso errore in futuro! Per evitare che ciò accada, aggiungiamo una sottoquery all'istruzione INSERT per verificare se esiste già una fattura con lo stesso nome order_id. Di seguito è riportata la nuova versione della query INSERT. La sottoquery aggiunta alla fine identifica le fatture già esistenti e la query esterna le scarta utilizzando l'operatore NOT IN. INSERT INTO invoice (date, client_id, amount, wine_name, order_id) SELECT order.date, order.client_id, o.quantity * w.price as amount, o.quantity || ’ bottles of ‘ || o.wine_name, o.order_id FROM order o JOIN wine ON wine.name = order.wine_name WHERE order.date = ‘2020-06-28’ AND o.order_id NOT IN (SELECT order_id FROM invoice i WHERE i.order_id=o.order_id ) Se volete migliorare le vostre competenze nell'uso delle istruzioni INSERT, UPDATE e DELETE, vi suggerisco il corso How to INSERT, UPDATE, and DELETE Data in SQL dove si possono vedere diversi esempi di queste istruzioni. Seconda tappa: le sottoquery nelle dichiarazioni UPDATE Come SELECT, l'istruzione UPDATE può avere una subquery in diversi punti o clausole. In un'istruzione UPDATE, le due clausole in cui le subquery sono più comunemente utilizzate sono SET e WHERE. La clausola SET è quella in cui si definisce il nuovo valore per la colonna che viene modificata dall'istruzione UPDATE. Per ottenere questo nuovo valore si può usare una subquery, che può essere selezionata da qualsiasi tabella o da qualsiasi subquery valida, purché restituisca un solo record con una sola colonna per ogni record aggiornato. Il tipo di dati della colonna restituita dalla subquery deve essere dello stesso tipo di quello della colonna da modificare. Creiamo un UPDATE per mantenere aggiornato il nostro stock di bottiglie di vino. Eseguiremo un UPDATE alla fine della giornata per riflettere ciò che abbiamo venduto oggi. Il codice sarà simile a questo: UPDATE wine w SET stock = stock - ( SELECT SUM (quantity) FROM order WHERE date = CURRENT_DATE AND order.wine_name = w.name ) Se non usassimo la clausola WHERE nella UPDATE, finiremmo per modificare tutti i record della tabella wine, compresi i record dei vini che non abbiamo venduto oggi. La subquery restituisce un NULL per tutti i vini non venduti oggi e noi erroneamente SET la colonna stock a NULL, poiché il risultato dell'espressione "stock - NULL" è NULL. Dobbiamo risolvere questo problema. Ci sono due approcci per risolvere il problema. Il primo consiste nel modificare l'espressione SUM(quantity) in modo che restituisca uno zero anziché un NULL. A tale scopo, è sufficiente utilizzare la funzione COALESCE, come in questo caso: UPDATE wine w SET stock = stock - ( SELECT coalesce(SUM (quantity), 0) FROM order WHERE date = CURRENT_DATE AND order.wine_name = w.name ) Il secondo approccio consiste nell'aggiungere una sottoquery nella clausola WHERE per modificare solo i vini che sono stati ordinati oggi e mantenere l'espressione SUM(quantity) così com'è. La seguente query mostra questo approccio: UPDATE wine w SET stock = stock - ( SELECT SUM (quantity) FROM order WHERE date = CURRENT_DATE AND order.wine_name = w.name ) WHERE w.name IN (SELECT order.wine_name FROM order WHERE date = CURRENT_DATE) Questa UPDATE non è ideale: la clausola SET utilizza una sottoquery correlata. Una subquery correlata è una subquery che il database deve eseguire più volte, una per ogni riga modificata nella tabella. Nel nostro esempio, la query ottiene l'indirizzo SUM(quantity) per ogni vino venduto oggi. Sebbene le subquery correlate possano essere potenti, è meglio evitarle quando è possibile, per una questione di best practice. In questo caso, possiamo evitare la sottoquery correlata utilizzando una clausola FROM nella UPDATE, come si può vedere di seguito: UPDATE wine w SET stock = stock - subquery.total_in_orders FROM ( SELECT wine_name, sum( order_quantity ) AS total_in_orders FROM order WHERE date = TODAY GROUP BY wine_name ) subquery WHERE w.name = subquery.wine_name Come risorsa supplementare, suggerisco l'articolo Beginners Guide to the SQL Subquery, dove è possibile imparare l'ABC delle subquery attraverso esempi di varia complessità. Terza tappa: le subquery nelle dichiarazioni DELETE Con l'istruzione DELETE, le subquery possono essere utilizzate solo all'interno di una clausola WHERE. Supponiamo di voler eliminare i record dei vini per i quali non abbiamo ricevuto alcun ordine negli ultimi 6 mesi. Possiamo creare una sottoquery che restituisca i vini venduti negli ultimi 6 mesi, quindi identificare i record che vogliamo rimuovere nella tabella wine utilizzando l'operatore NOT IN. Vediamo come funziona SQL: DELETE FROM wine w WHERE name NOT IN ( SELECT wine_name FROM order WHERE date >= CURRENT_DATE - interval ‘6 Months’ ) Supponiamo di voler eliminare i vini per i quali gli ordini totali negli ultimi 6 mesi sono stati inferiori a 10 unità. L'istruzione DELETE avrà questo aspetto: DELETE FROM wine w WHERE 10 > ( SELECT SUM(quantity) FROM order o WHERE o.wine_name = w.name AND date >= CURRENT_DATE - interval ‘6 Months’ ) In questo caso, la sottoquery restituisce la quantità di bottiglie ordinate negli ultimi 6 mesi per ciascun vino. Confrontando questa quantità con 10, possiamo determinare se un determinato vino deve essere eliminato. Arrivare alla fine del tour delle subquery Le subquery sono come dei blocchi di costruzione in SQL. Abbiamo visto che possono essere utilizzate in diversi modi, ad esempio nelle istruzioni SELECT o in qualsiasi istruzione di modifica come INSERT, UPDATE e DELETE. Abbiamo visto come utilizzare una subquery in diverse clausole e che esistono diversi modi di utilizzare le subquery nelle istruzioni INSERT, UPDATE e DELETE. Se volete saperne di più sulle subquery, vi suggerisco l'articolo Subquery vs. CTE: A SQL Primer, in cui viene spiegato un tipo di subquery chiamato CTE con molti esempi e dettagli. Infine, ci sono due corsi che contengono molte informazioni utili sulle subquery con numerosi esempi: SQL Basics dove si trova una sezione completa sulle subquery e How to INSERT, UPDATE, and DELETE Data in SQL che è un corso più avanzato. Andiamo a migliorare le vostre competenze in SQL! Tags: sql imparare sql sottoquery