5

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

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

4

6 回答 6

5

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

于 2008-08-20T01:28:08.567 回答
4

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

@Jonas:考虑到这一点:

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

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

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

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

于 2008-08-20T00:39:00.743 回答
2

诀窍是使用 ISNULL 进行 FULL JOIN,以从任一表中获取连接列。我通常将其包装到视图或派生表中,否则您还需要在 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 数字表(您确实有其中一个,不是吗?)。

于 2008-08-25T02:57:03.663 回答
1

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

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

于 2008-08-19T23:32:00.813 回答
1

关于降价 - 是的,这令人沮丧。编辑器确实预览了我的 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 函数非常混乱!但至少它有效——这让我现在很高兴;)

于 2008-08-20T01:09:30.110 回答
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
于 2017-06-29T14:03:35.943 回答