Pergunta

Eu tenho uma visualização de banco de dados de status mensal na qual preciso criar um relatório.Os dados na visualização são mais ou menos assim:

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 assim por diante

A visualização possui uma categoria de produto, uma receita, um ano e um mês.Quero criar um relatório comparando 2007 e 2008, mostrando 0 para os meses sem vendas.Portanto, o relatório deve ficar mais ou menos assim:

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


A principal coisa a notar é que o mês 1 só tem vendas em 2008 e, portanto, é 0 para 2007.Além disso, o mês 4 só não tem vendas em 2008, daí o 0, enquanto tem vendas em 2007 e ainda aparece.

Além disso, o relatório é, na verdade, para o ano financeiro - então, eu adoraria ter colunas vazias com 0 em ambos, se não houvesse vendas no mês 5, digamos, em 2007 ou 2008.

A consulta que recebi é mais ou menos assim:

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

O problema com esta consulta é que ela não retornaria a quarta linha nos meus dados de exemplo acima, já que não tivemos vendas em 2008, mas na verdade tivemos em 2007.

Esta é provavelmente uma consulta/problema comum, mas meu SQL está enferrujado depois de fazer desenvolvimento front-end por tanto tempo.Qualquer ajuda é muito apreciada!

Ah, aliás, estou usando o SQL 2005 para esta consulta, então, se houver algum novo recurso útil que possa me ajudar, me avise.

Foi útil?

Solução

A instrução Case é minha melhor amiga SQL.Você também precisa de uma tabela com o tempo para gerar sua rotação 0 em ambos os meses.

As suposições são baseadas na disponibilidade das seguintes tabelas:

vendas:Categoria | Receita | ENHH | Mês

e

tm:Ano | Mês (preenchido com todas as datas necessárias para relatórios)

Exemplo 1 sem linhas vazias:

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

RETORNOS:

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

Exemplo 2 com linhas vazias:Vou usar uma subconsulta (mas outras não) e retornarei uma linha vazia para cada combinação de produto e ano e mês.

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

RETORNOS:

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

Observe que a maioria das ferramentas de relatório fará essa funcionalidade de tabela cruzada ou matriz, e agora que penso nisso, o SQL Server 2005 tem uma sintaxe dinâmica que também fará isso.

Aqui estão alguns recursos adicionais.CASOhttp://www.4guysfromrolla.com/webtech/102704-1.shtmlSQL SERVER 2005 PIVOThttp://msdn.microsoft.com/en-us/library/ms177410.aspx

Outras dicas

@Christian - editor de descontos - UGH;especialmente quando a visualização e a versão final do seu post discordam...@Christian - junção externa completa - a junção externa completa é anulada pelo fato de haver referências ao SP1 na cláusula WHERE, e a cláusula WHERE é aplicada após JOIN.Para fazer uma junção externa completa com filtragem em uma das tabelas, você precisa colocar sua cláusula WHERE em uma subconsulta, para que a filtragem aconteça antes a junção ou tente construir todos os seus critérios WHERE na cláusula JOIN ON, o que é extremamente feio.Bem, na verdade não há uma maneira bonita de fazer isso.

@Jonas:Considerando isto:

Além disso, o relatório é, na verdade, para o exercício financeiro - então Eu adoraria ter colunas vazias com 0 em ambos se não houvesse vendas no mês 5, digamos, em 2007 ou 2008.

e o fato de que esse trabalho não pode ser feito com uma consulta bonita, eu definitivamente tentaria obter os resultados que você realmente deseja.Não adianta ter uma consulta feia e nem mesmo obter os dados exatos que você realmente deseja.;)

Então, sugiro fazer isso em 5 etapas:
1.crie uma tabela temporária no formato que você deseja que seus resultados correspondam
2.preencha-o com doze linhas, com 1-12 na coluna do mês
3.atualize a coluna "Este ano" usando sua lógica SP1
4.atualize a coluna "Ano Passado" usando sua lógica SP2
5.selecione na tabela temporária

Claro, acho que estou partindo do pressuposto de que você pode criar um procedimento armazenado para fazer isso.Você pode tecnicamente ser capaz de executar todo esse lote inline, mas esse tipo de feiura raramente é visto.Se você não conseguir criar um SP, sugiro que você recorra à junção externa completa por meio de subconsulta, mas isso não gerará uma linha quando um mês não tiver vendas em nenhum dos anos.

Sobre a redução - Sim, isso é frustrante.O editor visualizou minha tabela HTML, mas depois de postá-la ela desapareceu - então tive que remover toda a formatação HTML da postagem...

@kcrumley Acho que chegamos a conclusões semelhantes.Essa consulta facilmente fica muito feia.Na verdade, resolvi isso antes de ler sua resposta, usando uma abordagem semelhante (mas diferente).Tenho acesso para criar procedimentos e funções armazenados no banco de dados de relatórios.Criei uma função Table Valued aceitando uma categoria de produto e um ano financeiro como parâmetro.Com base nisso, a função preencherá uma tabela contendo 12 linhas.As linhas serão preenchidas com dados da visualização se houver vendas disponíveis; caso contrário, a linha terá valores 0.

Em seguida, uno as duas tabelas retornadas pelas funções.Como sei que todas as tabelas terão doze variações, é muito mais fácil e posso ingressar na categoria do produto e no mês:

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

Acho que sua abordagem é provavelmente um pouco mais limpa, pois a função GetFinancialYear é bastante confusa!Mas pelo menos funciona - o que me deixa feliz por enquanto ;)

O truque é fazer um FULL JOIN, com ISNULL para obter as colunas unidas de qualquer tabela.Normalmente, eu coloco isso em uma visualização ou tabela derivada, caso contrário, você também precisará usar ISNULL na cláusula 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

Isso deve atender aos requisitos mínimos - linhas com vendas em 2007 ou 2008, ou ambos.Para obter linhas sem vendas em nenhum dos anos, você só precisa INNER JOIN em uma tabela de 1 a 12 números (você faz tem um desses, não é?).

Posso estar errado, mas você não deveria usar uma junção externa completa em vez de apenas uma junção à esquerda?Dessa forma, você obterá colunas 'vazias' de ambas as tabelas.

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

Usando pivot e Dynamic Sql podemos alcançar este resultado

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

Resultado

Category    Month   2008    2007
----------------------------------
Bikes       1       10000   0
Bikes       2       12000   11000
Bikes       3       12000   11500
Bikes       4       0       15400
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top