SQL查询按月比较产品销售额

时间:2008-08-19 23:24:47

标签: sql sql-server sql-server-2005 reporting

我有一个每月状态数据库视图我需要基于构建报告。视图中的数据如下所示:

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


......等等

该视图包含产品类别,收入,年份和月份。我想创建一份比较2007年和2008年的报告,在没有销售的月份显示0。所以报告看起来应该是这样的:

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


需要注意的关键是第1个月仅在2008年有销售,因此2007年为0。此外,第4个月仅在2008年没有销售,因此为0,而它在2007年有销售但仍然显示。

此外,该报告实际上是财政年度 - 因此,如果在2007年或2008年的第5个月没有销售,我希望在两者中都有空列。

我得到的查询看起来像这样:

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

此查询的问题在于它不会返回上面示例数据中的第四行,因为我们在2008年没有任何销售,但我们实际上是在2007年。

这可能是一个常见的查询/问题,但是在进行前端开发这么长时间后,我的SQL生锈了。非常感谢任何帮助!

哦,顺便说一句,我正在使用SQL 2005进行此查询,所以如果有任何有用的新功能可以帮助我告诉我。

6 个答案:

答案 0 :(得分:4)

Case Statement是我最好的SQL朋友。您还需要一张时间表来在两个月内生成0转。

假设基于以下表格的可用性:

  

销售:类别|收入| Yearh |   月

  

tm:年|月(填充所有   报告所需的日期)

没有空行的示例1:

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

返回值:

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

空行示例2: 我将使用子查询(但其他人可能不会)并且将为每个产品和年月组合返回一个空行。

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

返回值:

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

请注意,大多数报表工具都会执行此交叉表或矩阵功能,现在我认为它SQL Server 2005具有也可以执行此操作的数据透视表语法。

以下是一些额外资源。 案件 http://www.4guysfromrolla.com/webtech/102704-1.shtml SQL SERVER 2005 PIVOT http://msdn.microsoft.com/en-us/library/ms177410.aspx

答案 1 :(得分:3)

@Christian - 降价编辑 - UGH;特别是当你的帖子的预览和最终版本不同意时...... @Christian - 完全外连接 - 完全外连接被WHERE子句中引用SP1的事实所推翻,并且在JOIN之后应用WHERE子句。要通过对其中一个表进行过滤来执行完全外部联接,您需要将WHERE子句放入子查询中,因此在连接之前进行过滤,或者尝试将所有WHERE条件构建到JOIN ON条款,这是非常难看的。嗯,实际上没有办法做到这一点。

@Jonas:考虑到这一点:

  

此外,该报告实际上是财政年度 - 所以如果在2007年或2008年的第5个月没有销售,我希望在两者中都有空列。

并且这项工作无法通过漂亮的查询完成,我肯定会尝试获得您真正想要的结果。没有必要进行丑陋的查询,甚至无法获得您真正想要的确切数据。 ;)

所以,我建议分5个步骤做这个:
1.以您希望结果匹配的格式创建临时表 2.填充十二行,月份栏中为1-12 3.使用SP1逻辑更新“今年”列 4.使用SP2逻辑更新“去年”列 5.从临时表中选择

当然,我想我的假设你可以创建一个存储过程来实现这一点。从技术上讲,您可以内联运行整批产品,但很少见到这种丑陋。如果你不能制作一个SP,我建议你通过子查询回到完整的外部联接,但是当一个月没有任何一年的销售时,它不会让你连续。

答案 2 :(得分:1)

关于减价 - 是的,这令人沮丧。编辑器确实预览了我的HTML表格,但在发布后它已经不见了 - 所以必须从帖子中删除所有HTML格式...

@kcrumley我认为我们得出了类似的结论。这个查询很容易变得丑陋。我实际上在阅读你的答案之前解决了这个问题,使用了类似的(但不同的方法)。我有权在报告数据库上创建存储过程和函数。我创建了一个表格值函数,接受产品类别和财务年度作为参数。基于此,该函数将填充包含12行的表。如果有任何可用的销售,则行将使用视图中的数据填充,如果不是该行将具有0值。

然后我加入函数返回的两个表。因为我知道所有桌子都有12个rove,所以它更容易分配,我可以加入产品类别和月份:

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

我认为您的方法可能更清晰,因为GetFinancialYear函数非常混乱!但至少它是有效的 - 这让我现在感到高兴;)

答案 3 :(得分:1)

诀窍是做一个FULL JOIN,用ISNULL来从任一个表中获取连接列。我通常将它包装到视图或派生表中,否则你也需要在WHERE子句中使用ISNULL。

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

这应该可以满足您的最低要求 - 2007年或2008年的销售额,或两者兼而有之。要获得任何一年中没有销售的行,您只需要INNER JOIN到1-12数字表(您执行have one of those,不是吗?)。

答案 4 :(得分:0)

我可能错了,但你不应该使用完整的外连接而不只是左连接吗?这样你就可以从两个表中获得“空”列。

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

答案 5 :(得分:0)

使用pivot和Dynamic Sql我们可以实现这个结果

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

结果

Category    Month   2008    2007
----------------------------------
Bikes       1       10000   0
Bikes       2       12000   11000
Bikes       3       12000   11500
Bikes       4       0       15400