【问题标题】:SQL query to compare product sales by monthSQL查询按月比较产品销售额
【发布时间】:2010-09-06 05:48:17
【问题描述】:

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

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 个月没有销售,我希望两者都为 0 的空列。

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

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 进行此查询,所以如果有任何有用的新功能可以帮助我,请告诉我。

【问题讨论】:

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


    【解决方案1】:

    Case Statement 是我最好的 sql 朋友。您还需要一个时间表来生成两个月的 0 rev。

    假设基于下表的可用性:

    销售:类别 |收入 |年份 | 月份

    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 枢轴 http://msdn.microsoft.com/en-us/library/ms177410.aspx

    【讨论】:

      【解决方案2】:

      @Christian -- 降价编辑器 -- UGH;特别是当您的帖子的预览和最终版本不一致时...... @Christian -- 完全外连接 -- 完全外连接被 WHERE 子句中对 SP1 的引用所推翻,并且 WHERE 子句在 JOIN 之后应用。要对其中一个表进行过滤并进行完全外连接,您需要将 WHERE 子句放入子查询中,以便过滤发生在 连接之前,或者尝试将所有 WHERE 条件构建到JOIN ON 子句,非常丑陋。好吧,实际上没有什么好办法来做这个。

      @Jonas:考虑到这一点:

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

      事实上,这项工作不能用漂亮的查询来完成,我肯定会尝试得到你真正想要的结果。有一个丑陋的查询甚至没有得到你真正想要的确切数据是没有意义的。 ;)

      所以,我建议分 5 步执行此操作:
      1. 以您希望结果匹配的格式创建一个临时表
      2. 用 12 行填充它,在月份列中使用 1-12
      3. 使用您的 SP1 逻辑更新“今年”列
      4. 使用您的 SP2 逻辑更新“去年”列
      5. 从临时表中选择

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

      【讨论】:

        【解决方案3】:

        诀窍是进行 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】:

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

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

          然后我将函数返回的两个表连接起来。因为我知道所有的桌子都会有十二圈,所以分配起来更容易,我可以加入产品类别和月份:

          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 函数非常混乱!但至少它有效——这让我现在很高兴;)

          【讨论】:

            【解决方案5】:

            我可能错了,但您不应该使用完全外连接而不是左连接吗?这样,您将从两个表中获得“空”列。

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

            【讨论】:

              【解决方案6】:

              使用 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
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-10-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多