Query SQL per confrontare le vendite di prodotti per mese
-
08-06-2019 - |
Domanda
Ho una visualizzazione del database dello stato mensile su cui devo creare un report basato.I dati nella vista sono simili a questi:
Category | Revenue | Yearh | Month
Bikes 10 000 2008 1
Bikes 12 000 2008 2
Bikes 12 000 2008 3
Bikes 15 000 2008 1
Bikes 11 000 2007 2
Bikes 11 500 2007 3
Bikes 15 400 2007 4
...E così via
La vista ha una categoria di prodotto, un fatturato, un anno e un mese.Voglio creare un report confrontando il 2007 e il 2008, mostrando 0 per i mesi senza vendite.Quindi il rapporto dovrebbe assomigliare a questo:
Category | Month | Rev. This Year | Rev. Last Year
Bikes 1 10 000 0
Bikes 2 12 000 11 000
Bikes 3 12 000 11 500
Bikes 4 0 15 400
La cosa fondamentale da notare è come il mese 1 abbia vendite solo nel 2008, e quindi è 0 per il 2007.Inoltre, il mese 4 non ha vendite solo nel 2008, da qui lo 0, mentre ha vendite nel 2007 e viene ancora visualizzato.
Inoltre, il report si riferisce effettivamente all'anno finanziario, quindi mi piacerebbe avere colonne vuote con 0 in entrambe se non ci fossero vendite, ad esempio, nel mese 5 per il 2007 o il 2008.
La query che ho ricevuto è simile a questa:
SELECT
SP1.Program,
SP1.Year,
SP1.Month,
SP1.TotalRevenue,
IsNull(SP2.TotalRevenue, 0) AS LastYearTotalRevenue
FROM PVMonthlyStatusReport AS SP1
LEFT OUTER JOIN PVMonthlyStatusReport AS SP2 ON
SP1.Program = SP2.Program AND
SP2.Year = SP1.Year - 1 AND
SP1.Month = SP2.Month
WHERE
SP1.Program = 'Bikes' AND
SP1.Category = @Category AND
(SP1.Year >= @FinancialYear AND SP1.Year <= @FinancialYear + 1) AND
((SP1.Year = @FinancialYear AND SP1.Month > 6) OR
(SP1.Year = @FinancialYear + 1 AND SP1.Month <= 6))
ORDER BY SP1.Year, SP1.Month
Il problema con questa query è che non restituirebbe la quarta riga nei dati di esempio sopra, poiché non abbiamo registrato vendite nel 2008, ma in realtà le abbiamo ottenute nel 2007.
Probabilmente si tratta di una query/problema comune, ma il mio SQL è arrugginito dopo aver svolto lo sviluppo front-end per così tanto tempo.Qualsiasi aiuto è molto apprezzato!
Oh, a proposito, sto usando SQL 2005 per questa query, quindi se ci sono nuove funzionalità utili che potrebbero aiutarmi fatemelo sapere.
Soluzione
Il Case Statement è il mio migliore amico SQL.Hai anche bisogno di una tabella temporale per generare il tuo 0 giri in entrambi i mesi.
Le ipotesi si basano sulla disponibilità delle seguenti tabelle:
saldi:Categoria | Entrate | Anno | Mese
E
tm:Anno | Mese (popolato con tutte le date richieste per il report)
Esempio 1 senza righe vuote:
select
Category
,month
,SUM(CASE WHEN YEAR = 2008 THEN Revenue ELSE 0 END) this_year
,SUM(CASE WHEN YEAR = 2007 THEN Revenue ELSE 0 END) last_year
from
sales
where
year in (2008,2007)
group by
Category
,month
RITORNA:
Category | Month | Rev. This Year | Rev. Last Year
Bikes 1 10 000 0
Bikes 2 12 000 11 000
Bikes 3 12 000 11 500
Bikes 4 0 15 400
Esempio 2 con righe vuote:Utilizzerò una sottoquery (ma altre potrebbero non farlo) e restituirò una riga vuota per ogni combinazione di prodotto e mese anno.
select
fill.Category
,fill.month
,SUM(CASE WHEN YEAR = 2008 THEN Revenue ELSE 0 END) this_year
,SUM(CASE WHEN YEAR = 2007 THEN Revenue ELSE 0 END) last_year
from
sales
Right join (select distinct --try out left, right and cross joins to test results.
product
,year
,month
from
sales --this ideally would be from a products table
cross join tm
where
year in (2008,2007)) fill
where
fill.year in (2008,2007)
group by
fill.Category
,fill.month
RITORNA:
Category | Month | Rev. This Year | Rev. Last Year
Bikes 1 10 000 0
Bikes 2 12 000 11 000
Bikes 3 12 000 11 500
Bikes 4 0 15 400
Bikes 5 0 0
Bikes 6 0 0
Bikes 7 0 0
Bikes 8 0 0
Tieni presente che la maggior parte degli strumenti di reporting eseguiranno questa funzionalità di tabella incrociata o matrice e ora che ci penso SQL Server 2005 ha una sintassi pivot che farà anche questo.
Ecco alcune risorse aggiuntive.CASOhttp://www.4guysfromrolla.com/webtech/102704-1.shtmlPIVOT DI SQL SERVER 2005http://msdn.microsoft.com/en-us/library/ms177410.aspx
Altri suggerimenti
@Christian - editor del ribasso - UGH;soprattutto quando l'anteprima e la versione finale del tuo post non sono d'accordo...@Christian - full external join - il full external join viene annullato dal fatto che sono presenti riferimenti a SP1 nella clausola WHERE e la clausola WHERE viene applicata dopo JOIN.Per eseguire un'unione esterna completa con filtro su una delle tabelle, è necessario inserire la clausola WHERE in una sottoquery, in modo che avvenga il filtraggio Prima il join, o prova a costruire tutti i tuoi criteri WHERE sulla clausola JOIN ON, il che è follemente brutto.Beh, in realtà non esiste un modo carino per farlo.
@Jonas:Considerando questo:
Inoltre, il rapporto si riferisce effettivamente all'anno finanziario, quindi Mi piacerebbe avere colonne vuote con 0 in entrambe se non ci fossero state vendite, ad esempio, nel mese 5 per il 2007 o il 2008.
e il fatto che questo lavoro non possa essere svolto con una bella query, proverei sicuramente a ottenere i risultati che desideri effettivamente.Non ha senso avere una brutta query e non ottenere nemmeno i dati esatti che desideri effettivamente.;)
Quindi, suggerirei di farlo in 5 passaggi:
1.crea una tabella temporanea nel formato in cui desideri che i risultati corrispondano
2.popolarlo con dodici righe, con 1-12 nella colonna del mese
3.aggiorna la colonna "Quest'anno" utilizzando la logica SP1
4.aggiorna la colonna "Ultimo anno" utilizzando la logica SP2
5.selezionare dalla tabella temporanea
Naturalmente, immagino di lavorare partendo dal presupposto che è possibile creare una procedura memorizzata per ottenere ciò.Potresti tecnicamente essere in grado di eseguire l'intero batch in linea, ma questo tipo di bruttezza si vede molto raramente.Se non riesci a creare un SP, ti suggerisco di ricorrere all'outer join completo tramite sottoquery, ma non ti darà una riga quando un mese non ha registrato vendite in nessuno dei due anni.
Riguardo al ribasso: sì, è frustrante.L'editor ha visualizzato l'anteprima della mia tabella HTML, ma dopo la pubblicazione non c'era più, quindi ho dovuto rimuovere tutta la formattazione HTML dal post...
@kcrumley Penso che abbiamo raggiunto conclusioni simili.Questa query diventa facilmente davvero brutta.In realtà ho risolto questo problema prima di leggere la tua risposta, utilizzando un approccio simile (ma allo stesso tempo diverso).Ho accesso per creare procedure e funzioni memorizzate nel database di reporting.Ho creato una funzione Tabella valori accettando una categoria di prodotto e un anno finanziario come parametro.In base a ciò la funzione popolerà una tabella contenente 12 righe.Le righe verranno popolate con i dati dalla vista se sono disponibili vendite, in caso contrario la riga avrà 0 valori.
Unisco quindi le due tabelle restituite dalle funzioni.Dato che so che tutte le tabelle avranno dodici roves, è molto più semplice e posso iscrivermi sulla categoria di prodotto e sul mese:
SELECT
SP1.Program,
SP1.Year,
SP1.Month,
SP1.TotalRevenue AS ThisYearRevenue,
SP2.TotalRevenue AS LastYearRevenue
FROM GetFinancialYear(@Category, 'First Look', 2008) AS SP1
RIGHT JOIN GetFinancialYear(@Category, 'First Look', 2007) AS SP2 ON
SP1.Program = SP2.Program AND
SP1.Month = SP2.Month
Penso che il tuo approccio sia probabilmente un po' più pulito dato che la funzione GetFinancialYear è piuttosto disordinata!Ma almeno funziona, il che per ora mi rende felice ;)
Il trucco sta nell'eseguire un FULL JOIN, con ISNULL per ottenere le colonne unite da entrambe le tabelle.Di solito lo inserisco in una vista o in una tabella derivata, altrimenti è necessario utilizzare ISNULL anche nella clausola WHERE.
SELECT
Program,
Month,
ThisYearTotalRevenue,
PriorYearTotalRevenue
FROM (
SELECT
ISNULL(ThisYear.Program, PriorYear.Program) as Program,
ISNULL(ThisYear.Month, PriorYear.Month),
ISNULL(ThisYear.TotalRevenue, 0) as ThisYearTotalRevenue,
ISNULL(PriorYear.TotalRevenue, 0) as PriorYearTotalRevenue
FROM (
SELECT Program, Month, SUM(TotalRevenue) as TotalRevenue
FROM PVMonthlyStatusReport
WHERE Year = @FinancialYear
GROUP BY Program, Month
) as ThisYear
FULL OUTER JOIN (
SELECT Program, Month, SUM(TotalRevenue) as TotalRevenue
FROM PVMonthlyStatusReport
WHERE Year = (@FinancialYear - 1)
GROUP BY Program, Month
) as PriorYear ON
ThisYear.Program = PriorYear.Program
AND ThisYear.Month = PriorYear.Month
) as Revenue
WHERE
Program = 'Bikes'
ORDER BY
Month
Questo dovrebbe darti i requisiti minimi: righe con vendite nel 2007 o nel 2008, o entrambi.Per ottenere righe senza vendite in uno dei due anni, devi solo eseguire l'INNER JOIN in una tabella di numeri da 1 a 12 (fai averne uno, non è vero?).
Potrei sbagliarmi, ma non dovresti utilizzare un join esterno completo anziché solo un join sinistro?In questo modo otterrai colonne "vuote" da entrambe le tabelle.
Utilizzando pivot e Dynamic Sql possiamo ottenere questo risultato
SET NOCOUNT ON
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
;With cte(Category , Revenue , Yearh , [Month])
AS
(
SELECT 'Bikes', 10000, 2008,1 UNION ALL
SELECT 'Bikes', 12000, 2008,2 UNION ALL
SELECT 'Bikes', 12000, 2008,3 UNION ALL
SELECT 'Bikes', 15000, 2008,1 UNION ALL
SELECT 'Bikes', 11000, 2007,2 UNION ALL
SELECT 'Bikes', 11500, 2007,3 UNION ALL
SELECT 'Bikes', 15400, 2007,4
)
SELECT * INTO #Temp FROM cte
Declare @Column nvarchar(max),
@Column2 nvarchar(max),
@Sql nvarchar(max)
SELECT @Column=STUFF((SELECT DISTINCT ','+ 'ISNULL('+QUOTENAME(CAST(Yearh AS VArchar(10)))+','+'''0'''+')'+ 'AS '+ QUOTENAME(CAST(Yearh AS VArchar(10)))
FROM #Temp order by 1 desc FOR XML PATH ('')),1,1,'')
SELECT @Column2=STUFF((SELECT DISTINCT ','+ QUOTENAME(CAST(Yearh AS VArchar(10)))
FROM #Temp FOR XML PATH ('')),1,1,'')
SET @Sql= N'SELECT Category,[Month],'+ @Column +'FRom #Temp
PIVOT
(MIN(Revenue) FOR yearh IN ('+@Column2+')
) AS Pvt
'
EXEC(@Sql)
Print @Sql
Risultato
Category Month 2008 2007
----------------------------------
Bikes 1 10000 0
Bikes 2 12000 11000
Bikes 3 12000 11500
Bikes 4 0 15400