2

我正在尝试构建一个多子查询查询,以便我可以将结果数据绑定到图表

这是我当前的查询:

SELECT TOP (100) PERCENT Sum(DBO.ORDERLINE.QTY)               AS UnitsSold, 
                         { fn HOUR(dbo.[Order].PaymentDate) } AS MyHour 
FROM   DBO.[ORDER] 
       INNER JOIN DBO.ORDERLINE 
               ON DBO.[ORDER].ORDERID = DBO.ORDERLINE.ORDERID 
WHERE  ( DBO.[ORDER].WEBSITEID = 2 ) 
       AND ( DBO.[ORDER].ORDERSTATUSID = 2 ) 
       AND ( Day(DBO.[ORDER].PAYMENTDATE) = 01 ) 
       AND ( Month(DBO.[ORDER].PAYMENTDATE) = 08 ) 
       AND ( Year(DBO.[ORDER].PAYMENTDATE) = 2013 ) 
GROUP  BY { fn HOUR(dbo.[Order].PaymentDate) } 

这会根据昨天的数据带回两列,UnitsSold 和 MyHour - 这很好用..

但是,我还想获得上周同一天和去年同一天的相同数据,我可以通过 c# 自己提供 MONTH/DAY/YEAR 值 - 我只是不确定如何执行这个复杂的查询。

如果您需要更多信息,请告诉我。

谢谢,

迈克尔

4

3 回答 3

4

NRF 零售日历是专门为解决销售比较的业务问题而创建的 -零售行业在 1930 年代通过将日历标准化为“4-5-4”日历解决了这个问题,其中每个三个月的第一个月有 4周,第二个有 5 周,第三个有 4 个,四个季度共 52 周,每年 364 天。他们通过定期制作 53 周年来解决另一个问题 -更多详细信息,请参见下文)。它考虑了闰年,并确保总是将星期五与星期五进行比较。

4-5-4 日历的目的是什么?

4-5-4 日历可作为零售行业的自愿指南,通过根据 4 周 - 5 周 - 4 周的格式将年份划分为月份,确保年份之间的销售可比性。日历的布局将假期排成一行,并确保可比月份中的周六和周日数量相同。因此,出于销售报告的目的,将相似天数与相似天数进行比较。


第 1 步:设置

通过将此日历建模到某个dbo.RetailTime表格中,您可以更轻松地比较从正确日期开始的销售额 -TY week 26 day 6比较LY week 26 day 6无论哪个实际日历日期是。日历是时间概念的抽象

像这样的东西:

public interface IRetailTime
{
    DateTime Date { get; } // does not have "time" info ("00:00:00")
    int DayHour { get; }
    int WeekDay { get; }
    int YearWeek { get; }
    int YearMonth { get; }
    int YearQuarter { get; }
    int Year { get; }
}

您可以通过添加字段QuarterDayQuarterWeekQuarterMonthMonthDay和来进一步充实这一点MonthWeek,具体取决于您的报告需求。零售商通常将Year与 连接起来YearWeek以标识每个日历周,因此 2013 年的第 26 周将是“201326”。

然后你编写一个脚本来将 NRF 日历数据导入到你的模型中,你可以创建一个函数、存储过程、视图或其他任何东西,为你提供RetailTimeIdforLY和,哎呀为什么不,为LLY(2 年前)字段(这可以都为空)为Id您的日历表中的每一个。

结果给你这样的东西(下面假设小时级别的粒度,每天 24 小时):

    RetailTimeId   LYId   LLYId
               1    NULL   NULL
               2    NULL   NULL
             ...     ...    ...
            8737       1   NULL
            8738       2   NULL
             ...     ...    ...
           17472    8737      1
           17473    8738      2
             ...     ...    ...

这为您提供了一个查找表(将其持久化到一个实际的dbo.RetailTimeLookup表中不会受到伤害),其中包含一个Id用于 LY 和 LLY 的表,用于dbo.RetailTime表中的每个 Id ( RetailTimeId)。您需要RetailTimeId列上的唯一索引,而不是其他两个列上的唯一索引,因为有 53 周年,您可能希望将第 53 周与同年的第 1 周进行比较。


第 2 步:与您的销售数据相关联

下一步是通过将“日期”部分(没有“时间”部分)与“时间”部分(只是小时)与匹配来查找Id与您对应的。这可能是一项昂贵的操作,您可能更喜欢安排一个通宵流程 (ETL),该流程将使用已查找的数据填充“SalesInfo”数据表,因此销售数据的格式如下:PaymentDateRetailTime.DateRetailTime.DayHourRetailTimeIdPaymentDate

public interface ISalesInfo
{
    int RetailTimeId { get; }
    int UnitsSold { get; }
}

所缺少的只是从上方加入 TY/LY/LLY 查找视图,您现在可以在“时间”维度上“切片”您的销售数据——我曾经查看今年的销售额去年的另一个视图最低粒度级别的销售,如下所示:

CREATE VIEW vwSALES_TY AS
BEGIN

    SELECT t.Id RetailTimeId,
           t.Year, 
           t.YearQuarter, 
           t.YearMonth, 
           t.YearWeek, 
           t.WeekDay, 
           t.DayHour,
           sales.UnitsSold Units                -- total units sold
           --,sales.CostAmount CostAmount       -- cost value of sold units
           --,sales.RetailAmount RetailAmount   -- full-price value of sold units
           --,sales.CurrentAmount CurrentAmount -- actual sale value of sold units
    FROM dbo.RetailTime t
        INNER JOIN dbo.SalesInfo sales ON t.Id = sales.RetailTimeId
    WHERE t.Year = 2013

END

CREATE VIEW vwSALES_LY AS
BEGIN

    SELECT t.Id RetailTimeId,
           t.Year, 
           t.YearQuarter, 
           t.YearMonth, 
           t.YearWeek, 
           t.WeekDay, 
           t.DayHour,
           sales.UnitsSold Units                -- total units sold
           --,sales.CostAmount CostAmount       -- cost value of sold units
           --,sales.RetailAmount RetailAmount   -- full-price value of sold units
           --,sales.CurrentAmount CurrentAmount -- actual sale value of sold units
    FROM dbo.RetailTime t
        INNER JOIN dbo.SalesInfo sales ON t.Id = sales.RetailTimeId
    WHERE t.Year = 2012

END

数字的含义

我将CostAmountRetailAmountCurrentAmount放在那里,因为从商业角度来看,知道已售出的单位是件好事,但它并不能告诉您这些销售的利润有多大——如果您以高折扣,您的毛利率 (GM%) 可能非常微薄甚至为负,而销售 TY 单位数量的一半可能会变成一个好得多的情况……如果库存以健康的速度转动 -每一点信息都与另一种、一种或另一种方式相关。

GM% 是- 这是每件西装都需要知道(1-CostAmount/CurrentAmount)*100的盈利能力数据。%Discount 是- 这就是您的销售折扣。仅“售出单位”的数字并不能说明什么。零售界有句谚语“销售是为了虚荣,利润是为了理智”。但我在飘。我们的想法是在您的详细销售数据中包含尽可能多的信息——这确实包括产品(理想的是 SKU)、销售点甚至客户信息(如果有的话)。任何遗漏的东西都永远不会出现在报告中。(1-CurrentAmount/RetailAmount)*100


第 3 步:……利润!

一个视图可以为您提供 TY 销售,另一个可以为您提供 LY 销售准备好排队,剩下要做的就是询问数据库

SELECT t.Year, 
       t.YearQuarter, 
       t.YearMonth, 
       t.YearWeek, 
       t.WeekDay, 
       t.DayHour,
       SUM(ISNULL(ty.Units,0)) UnitsTY,
       SUM(ISNULL(ly.Units,0)) UnitsLY
FROM dbo.RetailTime t
    INNER JOIN dbo.RetailTimeLookup lookup ON t.Id = lookup.RetailTimeId
    LEFT JOIN dbo.vwSALES_TY ty ON lookup.RetailTimeId = ty.RetailTimeId
    LEFT JOIN dbo.vwSALES_LY ly ON lookup.LYId = ly.RetailTimeId
WHERE t.Year = 2013

现在,这将为您提供 2013 年零售日历年每一天的每一小时的 TY 与 LY(保留 2012 年的历史,而 2013 年还没有记录),但这还不是您想要的,尽管所有信息都已经存在。

如果您将上述内容选择到临时表中(或将其用作子查询),则需要执行以下操作才能仅获取您感兴趣的数字:

SELECT t.DayHour,
       SUM(lw.UnitsTY) LastWeekUnitsTY,
       SUM(lw.UnitsLY) LastWeekUnitsLY,
       SUM(tw.UnitsTY) ThisWeekUnitsTY,
       SUM(tw.UnitsLY) ThisWeekUnitsLY
FROM (SELECT DayHour FROM #above GROUP BY DayHour) t
    LEFT JOIN (SELECT UnitsTY, UnitsLY 
               FROM #above 
               WHERE YearWeek = 25 AND WeekDay = 6) lw 
        ON t.DayHour = lw.DayHour
    LEFT JOIN (SELECT UnitsTY, UnitsLY 
               FROM #above 
               WHERE YearWeek = 26 AND WeekDay = 6) tw
        ON t.DayHour = tw.DayHour
GROUP BY t.DayHour

...但这只是比较周五的销售额。如果您想计算与上一年一致的本周至今 (WTD) 金额,您只需在两个子句中替换WeekDay = 6为。这就是我放s 和 a的原因。WeekDay <= 6WHERESUMGROUP BY

笔记

TY 和 LY 之间的百分比方差为(TY/LY - 1) * 100。如果您有多个销售点 (/store),则 LY 的商店数量可能少于 TY 的数量,这会阻碍比较。零售商已经解决了门对门百分比差异的另一个问题,通常称为“补偿增加”。这是通过不仅排列When(“时间”维度),而且还排列Where(“商店”维度)来实现的,仅计算LY开设的商店,忽略“非comp商店”。对于分解产品层次结构的报表,What还需要连接一些产品数据。


最后一件事

这个想法是将苹果与苹果进行比较——你需要提取这些数字是有原因的:每个零售商都想知道他们是否比 LY 数据有所改善。任何人都可以将两个数字相除并得出一个百分比数字。不幸的是,在现实的商业世界中,报告准确的数据并不总是那么简单。

免责声明:我在零售行业工作了 9 年。

于 2013-08-02T20:10:32.060 回答
1

DATE如果您将它们组合成一个正确的转换变量,您可以使用,而不是使用提供的值作为日期的一部分DATEADD()

SELECT TOP (100) PERCENT Sum(DBO.ORDERLINE.QTY)               AS UnitsSold, 
                         { fn HOUR(dbo.[Order].PaymentDate) } AS MyHour 
FROM   DBO.[ORDER] 
       INNER JOIN DBO.ORDERLINE 
               ON DBO.[ORDER].ORDERID = DBO.ORDERLINE.ORDERID 
WHERE  ( DBO.[ORDER].WEBSITEID = 2 ) 
       AND ( DBO.[ORDER].ORDERSTATUSID = 2 ) 
       AND ( DBO.[ORDER].PAYMENTDATE = @date 
              OR DBO.[ORDER].PAYMENTDATE = Dateadd(WEEK, -1, @date) 
              OR DBO.[ORDER].PAYMENTDATE = Dateadd(YEAR, -1, @date) ) 
GROUP  BY { fn HOUR(dbo.[Order].PaymentDate) } 

另请记住,如果您DATETIME在等式的任一端都有数据类型,那么您希望CAST他们DATE忽略该TIME部分。

于 2013-08-02T15:53:38.113 回答
1

如果我正确理解您的问题,您只需要一个带有 3 个结果的查询。

你可以使用一个union all.

这会将 3 个查询与不同的日期间隔结合起来。您将获得一个包含 3 行的结果集。

更新

您可以像这样组合查询(未经测试,不在我的电脑上)

SELECT TOP (100) PERCENT Sum(DBO.ORDERLINE.QTY) AS UnitsSold, { fn HOUR(dbo.[Order].PaymentDate) } AS MyHour 
FROM   DBO.[ORDER] 
       INNER JOIN DBO.ORDERLINE 
               ON DBO.[ORDER].ORDERID = DBO.ORDERLINE.ORDERID 
WHERE  ( DBO.[ORDER].WEBSITEID = 2 ) 
       AND ( DBO.[ORDER].ORDERSTATUSID = 2 ) 
       AND ( Day(DBO.[ORDER].PAYMENTDATE) = 01 ) 
       AND ( Month(DBO.[ORDER].PAYMENTDATE) = 08 ) 
       AND ( Year(DBO.[ORDER].PAYMENTDATE) = 2013 ) 
GROUP  BY { fn HOUR(dbo.[Order].PaymentDate) } 

union all

SELECT TOP (100) PERCENT Sum(DBO.ORDERLINE.QTY) AS UnitsSold, { fn HOUR(dbo.[Order].PaymentDate) } AS MyHour 
FROM   DBO.[ORDER] 
       INNER JOIN DBO.ORDERLINE 
               ON DBO.[ORDER].ORDERID = DBO.ORDERLINE.ORDERID 
WHERE  ( DBO.[ORDER].WEBSITEID = 2 ) 
       AND ( DBO.[ORDER].ORDERSTATUSID = 2 ) 
       AND ( Day(DBO.[ORDER].PAYMENTDATE) = 24 ) 
       AND ( Month(DBO.[ORDER].PAYMENTDATE) = 07 ) 
       AND ( Year(DBO.[ORDER].PAYMENTDATE) = 2013 ) 
GROUP  BY { fn HOUR(dbo.[Order].PaymentDate) } 

union all

SELECT TOP (100) PERCENT Sum(DBO.ORDERLINE.QTY) AS UnitsSold, { fn HOUR(dbo.[Order].PaymentDate) } AS MyHour 
FROM   DBO.[ORDER] 
       INNER JOIN DBO.ORDERLINE 
               ON DBO.[ORDER].ORDERID = DBO.ORDERLINE.ORDERID 
WHERE  ( DBO.[ORDER].WEBSITEID = 2 ) 
       AND ( DBO.[ORDER].ORDERSTATUSID = 2 ) 
       AND ( Day(DBO.[ORDER].PAYMENTDATE) = 01 ) 
       AND ( Month(DBO.[ORDER].PAYMENTDATE) = 08 ) 
       AND ( Year(DBO.[ORDER].PAYMENTDATE) = 2012 ) 
GROUP  BY { fn HOUR(dbo.[Order].PaymentDate) } 

您将获得 3 行数据。如果你愿意,你还可以添加一个假的列,说明图表的内容是什么(今天、上周、去年)

于 2013-08-02T15:54:18.627 回答