20th Aug 2024 Tempo di lettura: 20 minuti Progetto SQL per il portafoglio: Northwind Store Tihomir Babic imparare sql analisi dati Indice Il set di dati del negozio Northwind 1. Ispezione del database Ispezione dei nomi delle tabelle Ispezione delle informazioni sulle colonne Trovare le chiavi primarie e straniere Capire le relazioni tra le tabelle 2. Esplorazione dei dati Esplorazione della tabella degli ordini Conteggio delle righe Valori minimi e massimi Elenco delle categorie Conteggio dei valori distinti in ogni categoria Conteggio delle righe per valore di categoria Somma e media dei valori Esplorazione dei dati bonus Numero di prodotti Fatturato per anno Segmentazione dei clienti per Paese 3. Analisi avanzata dei dati e approfondimenti praticabili Vendite per canale Distribuzione del valore dell'ordine Clienti di alto e basso valore per canale Pronti per il vostro progetto di portafoglio SQL? I progetti di portafoglio SQL sono una parte importante della formazione di un analista di dati. Come si inizia un progetto e come si utilizzano le informazioni che si scoprono? Per rispondere a questa domanda utilizzeremo il database del negozio Northwind. Realizzare un progetto SQL solido è un elemento essenziale del processo di apprendimento di qualsiasi analista di dati. Un progetto SQL è un ottimo strumento per l'apprendimento, in quanto consente di utilizzare l'SQL su un set di dati reali. Questo è particolarmente importante se siete principianti e non avete la possibilità di lavorare con set di dati reali. Lavorando con più set di dati reali, si imparano i problemi che si possono incontrare nel mondo reale. Inoltre, la realizzazione di diversi progetti SQL per il vostro portfolio è sempre una bella cosa da vedere in un curriculum. La domanda è: come si prepara un portfolio di progetti SQL? Una volta trovato un set di dati online gratuito che si desidera analizzare, cosa si fa dopo? Risponderemo a queste domande utilizzando il database del negozio Northwind. Questo dataset è disponibile nel corso SQL Databases for Practice. Si tratta di uno dei sei dataset che includono i dati di un'università, del traffico di un blog, dei punteggi sportivi, di un negozio di musica e del Museum of Modern Art (MoMA). Questo corso fa parte della tracciaPratica su SQL , dove è possibile esercitarsi con aggregazioni, JOIN, subquery, CTE, CASE WHEN e altri importanti argomenti di SQL. Se avete bisogno di rinfrescare le vostre conoscenze su alcune di queste aree importanti per il reporting, provate il nostro corso. Come creare semplici report SQL corso. Prendiamo ora il dataset del negozio Northwind e utilizziamolo per un progetto SQL per un portfolio di analisti di dati. Il progetto sarà realizzato in PostgreSQL, ma tutto ciò che faccio è trasferibile ad altri database con piccole modifiche alla sintassi. Nel frattempo, consultate il nostro foglio informativo gratuito sull 'SQL per l'analisi dei dati. Il set di dati del negozio Northwind L'unica informazione che vi darò su questo database è che è composto da sei tabelle: categories - Un elenco di categorie di prodotti. channels - Un elenco di fonti attraverso le quali il negozio acquisisce clienti. customers - Un elenco dei clienti del negozio. order_items - Un elenco dei prodotti inclusi in ogni ordine. orders - Un elenco degli ordini effettuati dai clienti. products - Un elenco dei prodotti offerti dal negozio. Il resto delle informazioni lo raccoglieremo con il nostro progetto. In effetti, questa ispezione del database dovrebbe essere la fase iniziale di ogni progetto, quella che precede l'analisi. 1. Ispezione del database Questa fase della preparazione di un progetto SQL prevede la conoscenza dei dati, come i nomi delle tabelle e delle colonne, le chiavi primarie e quelle esterne, le relazioni tra le tabelle e i tipi di dati presenti in ogni tabella. Ispezione dei nomi delle tabelle Un modo primitivo per conoscere le tabelle del dataset è quello di trovarle nel browser del RDBMS in cui è stato importato il dataset, ad esempio PostgreSQL, SQL Server o MySQL. In PostgreSQL, è possibile scrivere questa query per ottenere un elenco di tutte le tabelle di un database: SELECT table_name FROM information_schema.tables WHERE table_schema NOT IN ('information_schema', 'pg_catalog') AND table_type = 'BASE TABLE' ORDER BY table_schema, table_name; Cerca i dati nella cartella information_schemadove si trovano i metadati del database. Poiché voglio il nome di ogni tabella, uso la vista tables dopo aver fatto riferimento allo schema e inserisco table_name in SELECT. La prima condizione in WHERE filtra gli schemi di sistema e lascia solo le tabelle definite dall'utente. La seconda condizione assicura che vengano elencate solo le tabelle di base, senza le viste e le altre tabelle. Ecco l'elenco delle tabelle del database Northwind: table_name categories channels customers order_items orders products Ispezione delle informazioni sulle colonne Ora vogliamo capire meglio i dettagli di ogni tabella. Conoscere le loro colonne è un buon inizio. Possiamo nuovamente interrogare information_schema per ottenere informazioni importanti sulle colonne: SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema NOT IN ('information_schema', 'pg_catalog') ORDER BY table_name, ordinal_position; Possiamo trovare i nomi delle tabelle, i nomi delle colonne e il tipo di dati di ciascuna colonna elencando table_name, column_name e data_type nella colonna SELECT. La colonna is_nullable ci informa se la colonna accetta i valori NULL. table_namecolumn_namedata_typeis_nullable categoriescategory_idintegerNO categoriescategory_namecharacter varyingNO categoriesdescriptiontextYES channelsidintegerNO channelschannel_namecharacter varyingNO customerscustomer_idintegerNO customersemailcharacter varyingNO customersfull_namecharacter varyingNO customersaddresscharacter varyingYES customerscitycharacter varyingYES customersregioncharacter varyingYES customerspostal_codecharacter varyingYES customerscountrycharacter varyingYES customersphonecharacter varyingYES customersregistration_datetimestamp without time zoneNO customerschannel_idintegerNO customersfirst_order_idintegerYES customersfirst_order_datetimestamp without time zoneYES customerslast_order_idintegerYES customerslast_order_datetimestamp without time zoneYES order_itemsorder_idintegerNO order_itemsproduct_idintegerNO order_itemsunit_pricenumericNO order_itemsquantitysmallintNO order_itemsdiscountnumericNO ordersorder_idintegerNO orderscustomer_idintegerNO ordersorder_datetimestamp without time zoneYES orderstotal_amountnumericNO ordersship_namecharacter varyingYES ordersship_addresscharacter varyingYES ordersship_citycharacter varyingYES ordersship_regioncharacter varyingYES ordersship_postalcodecharacter varyingYES ordersship_countrycharacter varyingYES ordersshipped_datetimestamp without time zoneYES productsproduct_idintegerNO productsproduct_namecharacter varyingNO productscategory_idintegerNO productsunit_pricenumericYES productsdiscontinuedbooleanNO In questo modo, abbiamo tutte le informazioni in un unico posto, il che rende più facile indagare. Per prima cosa, possiamo capire quali dati mostra ogni tabella vedendo i nomi delle colonne. I tipi di dati sembrano tutti logici. Ad esempio, non ci sono ID definiti come character varying. Pertanto, in questa fase non sarà necessario convertire i dati in un formato adeguato. Per quanto riguarda NULL, otteniamo alcune informazioni preziose sulle tabelle: La tabella categories permette che la descrizione della categoria sia NULL. La tabella channels non consente alcuna NULLs. La tabella customers consente NULLin molte colonne, compreso il Paese. Se si vuole creare una segmentazione geografica dei clienti, questi NULLpotrebbero creare problemi. L'output di cui sopra mostra che nessuna colonna della tabella order_items sono nullable. Per la tabella products è interessante notare che il prezzo unitario può essere NULL. Sembra un po' strano che la tabella orders abbia così tante colonne nullable. In pratica, è possibile avere un ID ordine e nessun'altra informazione sull'ordine. Dobbiamo tenerlo presente quando analizziamo questa tabella. Trovare le chiavi primarie e straniere La fase successiva dell'ispezione dei dati consiste nel capire come le tabelle lavorano insieme. Per farlo, dobbiamo prima trovare le chiavi primarie (PK) e le chiavi esterne (FK). Le PK ci mostrano quali colonne vengono utilizzate da una tabella per identificare in modo univoco i dati. In questo modo, è possibile comprendere meglio la struttura della tabella. Le FK ci mostrano quale colonna è collegata a una chiave primaria di un'altra tabella. Questa è la base per capire le relazioni tra le tabelle. È possibile elencare sia le PK che le FK utilizzando la query seguente: SELECT kcu.table_name, kcu.column_name, tc.constraint_type FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON kcu.constraint_name = tc.constraint_name AND kcu.constraint_schema = tc.constraint_schema WHERE tc.constraint_type IN ('PRIMARY KEY', 'FOREIGN KEY') ORDER BY kcu.table_name ASC, tc.constraint_type DESC; Questa volta dobbiamo utilizzare due viste della tabella information_schemae cioè table_constraints e key_column_usage. La vista table_constraints contiene informazioni sui vincoli di ogni tabella. La vista key_column_usage serve per ottenere informazioni sulle colonne con vincoli di chiave. Queste due viste sono unite da due colonne comuni: constraint_name (il nome del vincolo) e constraint_schema (il nome dello schema contenente il vincolo). Uso la clausola WHERE per produrre solo i vincoli di chiave primaria e di chiave esterna. Come ultimo passo, ordino l'output in ordine alfabetico per il nome della tabella e poi in ordine alfabetico inverso per il tipo di vincolo. Il codice produce questa tabella: table_namecolumn_nameconstraint_type categoriescategory_idPRIMARY KEY channelsidPRIMARY KEY customerscustomer_idPRIMARY KEY customerslast_order_idFOREIGN KEY customersfirst_order_idFOREIGN KEY customerschannel_idFOREIGN KEY order_itemsorder_idPRIMARY KEY order_itemsproduct_idPRIMARY KEY order_itemsorder_idFOREIGN KEY order_itemsproduct_idFOREIGN KEY ordersorder_idPRIMARY KEY orderscustomer_idFOREIGN KEY productsproduct_idPRIMARY KEY productscategory_idFOREIGN KEY Le tabelle categories e channels sono le uniche tabelle che hanno PK ma non FK. Tutte le altre tabelle hanno una PK e almeno una FK. L'unica eccezione è la tabella order_itemsche ha il vincolo PK su due colonne: order_id e product_id. In altre parole, la tabella ha una chiave primaria composita, il che significa che l'identificatore unico dei dati è la combinazione unica di due colonne. In questo caso, si tratta di mostrare un solo ID prodotto per ogni singolo ordine. Un'analisi più attenta rivela che le stesse colonne sono anche una chiave esterna per la stessa tabella. Ciò significa che anche le chiavi primarie sono estranee, poiché provengono da altre tabelle. Capire le relazioni tra le tabelle L'elenco delle PK e delle FK ci dà già un'idea delle relazioni tra le tabelle. Tuttavia, possiamo comprenderle meglio mostrando le tabelle e le colonne padre e figlio delle chiavi esterne. Perché? Una chiave esterna è sempre la chiave primaria di un'altra tabella. L'elencazione di tutte le tabelle e le colonne padre e figlio rappresenta un miglioramento rispetto alla query precedente, in quanto è possibile vedere facilmente le colonne condivise tra le tabelle. Queste informazioni sono utili per capire le relazioni tra le tabelle, per sapere quali colonne si possono usare per unire le tabelle e se queste possono essere unite direttamente o attraverso un'altra tabella. Ecco il codice: SELECT ccu.table_name AS parent_table, ccu.column_name AS parent_column, kcu.table_name AS child_table, kcu.column_name AS child_column FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' ORDER BY kcu.table_name; Nella query, utilizziamo le informazioni provenienti dalle tabelle table_constraints, key_column_usage, e constraint_column_usage delle viste. La vista constraint_column_usage mostra le colonne utilizzate da un vincolo. La useremo per mostrare la tabella e la colonna padre dell'FK, cioè la tabella e la colonna in cui questo FK è effettivamente una PK. I dati di key_column_usage mostreranno la tabella e la colonna di un FK. parent_tableparent_columnchild_tablechild_column ordersorder_idcustomerslast_order_id ordersorder_idcustomersfirst_order_id channelsidcustomerschannel_id ordersorder_idorder_itemsorder_id productsproduct_idorder_itemsproduct_id customerscustomer_idorderscustomer_id categoriescategory_idproductscategory_id Dall'output precedente, si nota che la tabella orders è direttamente collegata alla tabella customers attraverso le colonne order_id (PK) e customer_id (FK). È collegata alla tabella order_items tramite order_id (PK). La tabella channels è collegata direttamente solo alla tabella customers. Abbiamo già stabilito che le chiavi primarie della tabella order_items sono anche chiavi esterne. Dalla tabella precedente, possiamo vedere che provengono dalle tabelle orders e products. La tabella categories è collegata solo alla tabella products. 2. Esplorazione dei dati Quando si inizia a lavorare con un nuovo database, il primo passo dovrebbe essere l'esplorazione dei dati per ottenere alcune informazioni di base su ogni tabella del database. È buona norma esaminare quanto segue: Contare le righe. Trovare i valori minimi e massimi (soprattutto per le date). Elencare categorie distinte. Conteggio dei valori distinti in ogni categoria. Conteggio delle righe per ogni valore della categoria. Somme e medie per valori numerici chiave. Questo non è un elenco esaustivo e può cambiare a seconda della tabella che si sta esplorando. Tuttavia, è un minimo e dovrebbe funzionare per la maggior parte delle tabelle. In generale, questa fase si riduce ad alcune operazioni fondamentali di aggregazione e raggruppamento dei dati. Vi mostrerò come funziona su una tabella del nostro database e poi potrete esplorare altre tabelle nello stesso modo. Esplorazione della tabella degli ordini Conteggio delle righe Come previsto, qui utilizziamo la funzione di aggregazione COUNT(): SELECT COUNT(*) AS number_of_rows FROM orders; Io uso la funzione COUNT() con l'asterisco; questa conterà tutte le righe, compresi i NULL. number_of_rows 11,618 La tabella orders ha 11.618 righe. In altre parole, ci sono 11.618 ordini. Valori minimi e massimi Vediamo quali colonne sono adatte a essere utilizzate con le funzioni MIN() e MAX(). Perfetto - possiamo trovare le date più vecchie e più recenti in cui un ordine è stato effettuato e spedito: SELECT MIN(order_date) AS oldest_order_date, MAX(order_date) AS latest_order_date, MIN(shipped_date) AS oldest_shipped_date, MAX(shipped_date) AS latest_shipped_date FROM orders; Dall'output, possiamo vedere che il primo ordine risale a 2017-01-01 e l'ultimo a 2024-06-24. Per quanto riguarda le date di spedizione, la prima risale a 2017-01-01 e la più recente a 2024-06-23. oldest_order_datelatest_order_dateoldest_shipped_datelatest_shipped_date 2017-01-01 0:00:002024-06-24 0:00:002017-01-06 0:00:002024-06-23 0:00:00 Elenco delle categorie Per fare questo non abbiamo bisogno di funzioni aggregate: SELECT DISTINCT ship_country FROM orders ORDER BY ship_country; Nella query, usiamo la clausola DISTINCT per mostrare ogni paese una sola volta. Ecco un elenco dei Paesi in questa tabella: ship_country Argentina Austria Belgium Brazil Canada Denmark Finland France Germany Ireland Italy Mexico Norway Poland Portugal Spain Sweden Switzerland UK USA Venezuela Sarà utile anche visualizzare un elenco di città: SELECT DISTINCT ship_city FROM orders ORDER BY ship_city; Poiché ci sono molte città, ecco un elenco parziale: ship_city Aachen Abilene Achille Adelphi Adrian Akron Albany Alberton Albuquerque … Yucca Valley Conteggio dei valori distinti in ogni categoria Vediamo ora il numero di Paesi in cui Northwind ha consegnato le spedizioni: SELECT COUNT(DISTINCT ship_country) AS number_of_countries FROM orders; Utilizziamo nuovamente COUNT(), ma questa volta aggiungiamo DISTINCT e facciamo riferimento alla colonna che vogliamo contare. Il risultato mostra che ci sono 21 paesi diversi in cui l'azienda effettua consegne: number_of_countries 21 Possiamo fare lo stesso per le città: SELECT COUNT(DISTINCT ship_city) AS number_of_cities FROM orders; Ci sono 494 città uniche: number_of_cities 494 Anche se non le abbiamo elencate come categoria separata, sapere quanti clienti hanno effettuato ordini sarà utile: SELECT COUNT(DISTINCT customer_id) AS number_of_customers_with_orders FROM orders; Utilizziamo COUNT() per contare gli ID dei clienti. Naturalmente, abbiamo bisogno di DISTINCT, poiché si può prevedere che alcuni clienti compaiano più volte, cioè ogni volta che effettuano un ordine. Ci sono 761 clienti unici che hanno ordinato almeno una volta dall'azienda. number_of_customers_with_orders 761 Conteggio delle righe per valore di categoria A questo scopo, occorre elencare ogni valore di categoria (come abbiamo fatto in precedenza) e contare il numero di righe per ogni valore. Ad esempio, si otterrà il numero di ordini per paese: SELECT ship_country, COUNT(*) AS number_of_orders FROM orders GROUP BY ship_country ORDER BY number_of_orders DESC; Seleziono la colonna ship_country e poi uso COUNT(*) per contare tutte le righe. Affinché il conteggio avvenga per paese, è necessario introdurre la clausola GROUP BY ship_country. Si ottiene così il numero di ordini per paese. L'output mostra che gli Stati Uniti sono il mercato più grande, con 9.789 ordini: ship_countrynumber_of_orders USA9,789 France236 Brazil233 Germany230 UK144 Spain123 Mexico118 Venezuela103 Argentina87 Canada82 Italy64 Austria58 Portugal58 Belgium56 Denmark56 Finland47 Norway30 Sweden28 Poland27 Ireland26 Switzerland23 Somma e media dei valori Se i dati sono adeguati, è possibile trovare medie e totali di valori numerici. È possibile farlo per la colonna total_amount, che rappresenta il valore di un ordine: SELECT SUM(total_amount) AS orders_total_value, AVG(total_amount) AS average_order_value FROM orders; Per mostrare questi due calcoli, utilizzate le funzioni aggregate SUM() e AVG(). Possiamo vedere che il fatturato totale del negozio è di poco superiore a 19 milioni. Il valore medio dell'ordine è di 1.636,15: orders_total_valueaverage_order_value 19,008,819.691,636.15 Esplorazione dei dati bonus Come ho già detto, le aggregazioni precedenti dovrebbero essere il minimo che si possa fare per ogni tabella del database. Questa esplorazione dei dati va un po' oltre (ma non troppo) le semplici aggregazioni e GROUP BY. Sebbene questa sia ancora la base, è possibile utilizzare altri concetti, come filtrare i dati (utilizzando WHERE e/o HAVING), estrarre parti di data o l'ora, utilizzare CASE WHEN per etichettare i dati e così via. Esaminiamo alcuni esempi. Numero di prodotti Possiamo utilizzare la funzione COUNT() per trovare il numero totale di prodotti venduti dal negozio. SELECT COUNT(*) AS number_of_active_products FROM products WHERE discontinued IS FALSE; La clausola WHERE contiene una condizione per mostrare solo i prodotti che non sono fuori produzione, cioè quelli che il negozio vende attualmente. Dalla sezione precedente, sappiamo che la colonna discontinued è di tipo booleano. Dobbiamo quindi utilizzare l'operatore IS FALSE per includere nel conteggio solo i prodotti non fuori produzione. Il conteggio è 69: number_of_active_products 69 Fatturato per anno Un semplice report che mostra i ricavi per anno può essere creato utilizzando SUM(): SELECT EXTRACT(YEAR FROM order_date) AS revenue_year, SUM(total_amount) AS revenue FROM orders GROUP BY revenue_year; Utilizziamo la funzione EXTRACT() per ottenere solo gli anni a partire dalle date degli ordini. Quindi, sommiamo gli importi totali di tutti gli ordini e raggruppiamo per anno per mostrare i valori di ciascun anno separatamente. Dai risultati si evince che l'anno di punta dell'azienda è stato il 2018. L'anno con il fatturato più basso è il 2024, ma questo potrebbe essere dovuto al fatto che l'anno non è ancora terminato (al momento dell'analisi). Un'altra cosa interessante è che non ci sono dati sulle entrate per gli anni 2019-2022. Occorre verificare se i dati mancano per un motivo o se si tratta di un errore. revenue_yearrevenue 20173,088,759.84 20189,368,330.91 20234,646,048.11 20241,905,680.83 Segmentazione dei clienti per Paese Disponiamo di dati sui Paesi dei clienti, quindi una panoramica del numero di clienti in ciascun Paese sarebbe informativa. Inoltre, non ci interessano i clienti che non hanno effettuato un ordine, perché potrebbero gonfiare artificialmente il numero di clienti. No, vogliamo solo i clienti che ordinano da noi. Ecco la query: SELECT country, COUNT(*) AS number_of_customers FROM customers WHERE first_order_id IS NOT NULL GROUP BY country ORDER BY number_of_customers DESC; Selezioniamo e raggruppiamo per paese e utilizziamo la funzione aggregata COUNT() per trovare il numero di clienti. Se i dati della colonna first_order non sono nulli, allora questo cliente ha effettuato almeno un ordine; questa è la condizione che dobbiamo utilizzare in WHERE. I dati sono ordinati dal numero di clienti più alto a quello più basso. L'output mostra che il mercato più grande di Northwind in termini di clienti è quello degli Stati Uniti. Si può anche concludere che si tratta del mercato più grande in termini di fatturato. countrynumber_of_customers USA697 Germany8 France8 Brazil8 UK5 Venezuela4 Spain4 Mexico4 Argentina3 Canada3 Belgium2 Denmark2 Portugal2 Finland2 Italy2 Austria2 Sweden1 Poland1 Ireland1 Switzerland1 Norway1 3. Analisi avanzata dei dati e approfondimenti praticabili Quanto fatto finora è un buon inizio. Tuttavia, l'analisi dei dati in un progetto SQL per un portafoglio non dovrebbe fermarsi alla semplice aggregazione ed esplorazione per ogni tabella. Ora andremo oltre per scrivere query più complesse che ci permetteranno di agire e migliorare l'attività di Northwind. Ad esempio, potremmo voler vedere come cambiano le vendite a seconda del canale. Vediamo cosa otteniamo e poi decidiamo quali saranno i nostri prossimi passi. Vendite per canale Per ogni canale, vogliamo mostrare le vendite totali, il valore medio degli ordini, il numero totale di ordini e il numero di clienti unici. Vogliamo anche classificare i canali in base alle vendite totali. Ecco la query: SELECT ch.channel_name, SUM(o.total_amount) AS total_sales, AVG(o.total_amount) AS average_order_value, COUNT(o.order_id) AS total_orders, COUNT(DISTINCT o.customer_id) AS unique_customers, RANK() OVER (ORDER BY SUM(o.total_amount) DESC) AS sales_rank FROM channels ch LEFT JOIN customers c ON ch.id = c.channel_id LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY ch.channel_name ORDER BY sales_rank; Questa query utilizza diverse aggregazioni: SUM() per calcolare le vendite totali, AVG() per il valore medio degli ordini e COUNT() per il numero totale di ordini e (con DISTINCT) per i clienti unici. Quindi, si utilizza la funzione finestra RANK() per classificare i canali in base alle vendite totali. Poiché le funzioni finestra vengono eseguite prima dell'aggregazione, non è possibile utilizzare semplicemente la colonna total_sales nella funzione finestra. È invece necessario replicare l'intero calcolo, un'operazione semplice che si completa con il copia e incolla. Utilizziamo i dati delle tabelle channels e orders. Tuttavia, non posso unire direttamente queste due tabelle perché non hanno una colonna condivisa. Dobbiamo invece unirle tramite la tabella customers. Ecco il risultato: channel_nametotal_salesaverage_order_valuetotal_ordersunique_customerssales_rank Organic Search14,003,046.951,603.108,7355651 Direct2,436,649.061,799.591,354912 Referral1,034,734.451,645.05629463 Social837,378.771,824.35459324 Paid Search483,824.241,645.66294195 Email213,186.221,450.2514786 Vediamo che la maggior parte delle vendite di Northwind proviene dalla ricerca organica. Non ci sono contraddizioni nei dati: la classifica delle vendite totali riflette anche il numero di ordini e di clienti unici. È interessante notare che il canale della ricerca organica non ha il valore medio dell'ordine più alto. Questo richiede un miglioramento! Immaginiamo una strategia di incremento dei ricavi che si concentri sull'aumento del valore medio degli ordini di tutti i canali piuttosto che sulle nuove acquisizioni. Probabilmente non potremo adottare un approccio uniforme basato solo sul canale di marketing. All'interno di ogni segmento, potrebbero esserci clienti con abitudini di spesa diametralmente opposte. Questo, possiamo supporre, vale soprattutto per la ricerca organica, che è un canale così vasto. Pertanto, è necessario conoscere meglio la distribuzione del valore dell'ordine per ciascun canale. Distribuzione del valore dell'ordine Calcoliamo il valore medio dell'ordine, la mediana, il quartile superiore, il quartile inferiore e la deviazione standard per ciascun canale: SELECT ch.channel_name, AVG(total_amount) AS average_order_value, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total_amount) AS median_order_value, PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY total_amount) AS upper_quartile_order_value, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY total_amount) AS lower_quartile_order_value, STDDEV(total_amount) AS order_value_stddev FROM channels ch LEFT JOIN customers c ON ch.id = c.channel_id LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY ch.channel_name; Il valore medio dell'ordine è calcolato da AVG(). La mediana (o 50° percentile), il quartile superiore e il quartile inferiore sono calcolati utilizzando la funzione aggregata di set ordinati PERCENTILE_CONT(), con il percentile specificato tra parentesi. Per il calcolo del percentile, i dati devono essere ordinati in ordine crescente; a tale scopo si utilizza la clausola WITHIN GROUP. Dopo il calcolo del percentile, STDDEV() viene utilizzato per calcolare la deviazione standard. Ecco l'output: channel_nameaverage_order_valuemedian_order_valueupper_quartile_order_valuelower_quartile_order_valueorder_value_stddev Direct1,799.591,005.902,166.80517.402,323.63 Email1,450.25960.001,970.41492.501,655.97 Organic Search1,603.101,007.702,018.20480.001,916.39 Paid Search1,645.661,083.002,104.40486.551,813.22 Referral1,645.051,064.002,034.50482.901,969.01 Social1,824.351,122.802,123.20559.902,319.10 Analizziamo le metriche del primo canale. La deviazione standard del canale Direct è 2.323,63, superiore al valore medio. Ciò indica un'elevata variabilità, ossia la presenza di valori anomali o di un'ampia gamma di ordini. I quartili inferiore e superiore mostrano che il 50% degli ordini è compreso tra 517,40 e 2.166,80. Tuttavia, un'elevata deviazione standard significa che molti ordini sono al di fuori di questo intervallo. La mediana è significativamente più bassa della media, il che indica che la distribuzione è a destra, cioè con un basso numero di valori di ordine elevato che aumentano la media. Possiamo analizzare ogni canale allo stesso modo. Questo ci dà un'altra idea. Per creare strategie personalizzate per aumentare i ricavi, possiamo segmentare ulteriormente i clienti in base al quartile superiore e inferiore. Considereremo tutti i clienti al di sopra del quartile superiore come clienti che spendono molto, mentre quelli che spendono poco saranno i clienti al di sotto del quartile inferiore. In questo modo, possiamo adattare la nostra strategia a ciascun canale e alle abitudini di spesa di ciascun gruppo. Clienti di alto e basso valore per canale Eseguirò questo calcolo per un solo canale di marketing. Potete farlo per tutti gli altri, perché il codice rimarrà lo stesso; cambierete solo il canale in WHERE. I clienti nel quartile superiore sono clienti di alto valore e vogliamo che siano inseriti in un elenco separato: WITH customer_order_totals AS ( SELECT c.customer_id, c.full_name, SUM(o.total_amount) AS total_order_value, ch.channel_name FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN channels ch ON c.channel_id = ch.id WHERE ch.channel_name = 'Direct' GROUP BY c.customer_id, c.full_name, ch.channel_name ), upper_quartile_value AS ( SELECT PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY total_order_value) AS upper_quartile FROM customer_order_totals ) SELECT cot.customer_id, cot.full_name, cot.total_order_value, cot.channel_name FROM customer_order_totals cot JOIN upper_quartile_value uqv ON cot.total_order_value >= uqv.upper_quartile ORDER BY cot.total_order_value DESC; La prima CTE calcola il valore totale dell'ordine di ciascun cliente utilizzando SUM() e unendo le tabelle customers e ordini. In WHERE, filtriamo tutti i canali tranne Direct. La seconda CTE calcola il quartile superiore in modo analogo. La terza SELECT quindi CROSS JOINs le due CTE per trovare tutti i clienti il cui valore dell'ordine è superiore al quartile superiore. Ecco le prime dieci righe dell'output: customer_idfull_nametotal_order_valuechannel_name 134Barry Michael79,371.50Direct 152Carolann Williams64,365.21Direct 7Frédérique Citeaux61,865.74Direct 17Sven Ottlieb57,251.14Direct 64Sergio Gutiérrez55,140.75Direct 490Alice Blevins54,736.24Direct 8Martín Sommer54,499.55Direct 303Gregory Mack52,554.20Direct 316Jeff Heard51,976.31Direct 129Stephan Bufford50,868.70Direct In totale, l'output contiene 23 clienti. È possibile utilizzare queste informazioni per individuare questi clienti di alto valore. Ad esempio, possiamo creare programmi di fidelizzazione in cui questi clienti possono ottenere sconti speciali, servizi personalizzati, punti da riscattare per gli acquisti, programmi di affiliazione VIP e così via. Allo stesso modo, possiamo elencare i clienti i cui ordini sono inferiori al quartile inferiore: WITH customer_order_totals AS ( SELECT c.customer_id, c.full_name, SUM(o.total_amount) AS total_order_value, ch.channel_name FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN channels ch ON c.channel_id = ch.id WHERE ch.channel_name = 'Direct' GROUP BY c.customer_id, c.full_name, ch.channel_name ), lower_quartile_value AS ( SELECT PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY total_order_value) AS lower_quartile FROM customer_order_totals ) SELECT cot.customer_id, cot.full_name, cot.total_order_value, cot.channel_name FROM customer_order_totals cot, lower_quartile_value lqv WHERE cot.total_order_value <= lqv.lower_quartile ORDER BY cot.total_order_value ASC; Questa query è quasi identica alla precedente, ma questa volta calcoliamo il quartile inferiore. Inoltre, cerchiamo i clienti con ordini inferiori a tale valore. Ancora una volta, la query restituisce 191 clienti. Ecco le prime dieci righe: customer_idfull_nametotal_order_valuechannel_name 939Shannon Aguilar98.40Direct 997Barbra Armstrong251.50Direct 687Andrew Scott452.90Direct 787Dennis Myer912.00Direct 917Les Allen991.40Direct 921Shelby Turgeon1,162.25Direct 560Nancy Wiggins1,425.80Direct 678Tracey Thomas2,555.20Direct 756Dora Rowlands2,713.50Direct 715George Scott2,906.50Direct Questi clienti devono essere affrontati in modo diverso, perché devono essere motivati a spendere di più. Le strategie per aumentare le entrate da questi clienti possono includere pacchetti di prodotti personalizzati, spedizione gratuita al di sopra di un certo importo, aumento degli sconti per soglie d'ordine più elevate o offerta di un regalo gratuito al di sopra di un certo importo dell'ordine. Ora fate la stessa analisi per tutti gli altri canali e pensate a quali tattiche potreste usare per aumentare il valore degli ordini da quel canale. Pronti per il vostro progetto di portafoglio SQL? In questo articolo abbiamo mostrato come preparare un progetto di portafoglio SQL utilizzando il database del negozio Northwind. Naturalmente, se siete abbastanza creativi, potete anche creare il vostro set di dati. Completare un progetto SQL per il vostro portfolio è una parte importante della preparazione al processo di assunzione. I portfolio sono un'ottima vetrina delle vostre capacità pratiche di analisi dei dati in SQL. Il progetto di cui sopra è solo un esempio. Ci sono molte altre idee di analisi che potete provare nel nostro corso SQL Databases for Practice, che vi consigliamo vivamente! Tags: imparare sql analisi dati