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.

È stato utile?

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.

http://en.wikipedia.org/wiki/Join_(SQL)#Full_outer_join

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top