【问题标题】:Average monthly count over a date range一个日期范围内的平均每月计数
【发布时间】:2014-06-09 20:03:07
【问题描述】:

考虑下表。 Start_Date 是水果进入库存的时间,End_Date 是水果被处理掉的时间。请注意,这些日期范围可能会重叠。例如,从 2014-01-016 到 2014-02-01,库存中有 20 个苹果。

+------------------------------------------+
|           [HISTORY_TABLE]                |
+------------+------------+----------+-----+
| Start_Date | End_Date   | Type     | QTY |
+------------+------------+----------+-----+
| 2013-12-16 | 2014-02-01 | Apple    | 12  |
| 2014-01-16 | 2014-06-01 | Apple    |  8  |
| 2014-01-16 | 2014-04-11 | Banana   |  5  |
| 2014-03-16 | 2014-04-16 | Banana   |  7  |
| 2014-02-16 | 2014-03-01 | Orange   | 24  |
| 2013-02-24 | 2014-05-01 | Orange   |  2  |
+------------+------------+----------+-----+

我感兴趣的是每种水果的平均每月计数。对于 1 月份的苹果,每日累计苹果总数为:

((12 apples * 15 days) + (20 apples * 16 days) = 
(180 apple days + 320 apple days) = 500 apple days

由于 1 月有 31 天,平均值为:

500 / 31 = 16.13

因此,1 月份的平均可用苹果数为 16.13。

可能存在具有相同日期、类型和数量的水果类型,但假设每个记录都是唯一的水果计数。我正在寻找的最终结果将如下所示(除了所有水果。)结果中的数字是准确的(至少我认为它们是......我手工计算的):

+-----------------------------------------+
|  [RESULTS]                              |
+-------+------+------------+-----+-------+
| Month | Year | Fruit Type | QTY |  Avg  |
+-------+------+------------+-----+-------+
|  12   | 2013 | Apple      | 192 |  6.19 |
|  01   | 2014 | Apple      | 500 | 16.13 |
|  02   | 2014 | Apple      | 236 |  8.43 |
|  03   | 2014 | Apple      | 248 |  8.00 |
|  04   | 2014 | Apple      | 240 |  8.00 |
|  05   | 2014 | Apple      | 248 |  8.00 |
|  06   | 2014 | Apple      |   8 |  0.27 |
+-------+------+------------+-----+-------+

一些设置初始数据的代码:

IF OBJECT_ID('tempdb..#LocalTempFruitTable', 'U') IS NOT NULL
    DROP TABLE #LocalTempFruitTable

CREATE TABLE #LocalTempFruitTable(
    Start_Date date,
    End_Date date,
    Type varchar(50),
    QTY int)

INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2013-12-16','2014-02-01','Apple','12')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-01-16','2014-06-01','Apple','8')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-01-16','2014-04-11','Banana','5')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-03-16','2014-04-16','Banana','7')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-02-16','2014-03-01','Orange','24')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2013-02-24','2014-05-01','Orange','2')

SELECT * FROM #LocalTempFruitTable

【问题讨论】:

  • 绝对不是“家庭作业”...我正在尝试构建的真实生活报告比这复杂得多。使用 CTE,我尝试从我正在查看的日期范围内的每一天的表格开始,然后将它与当天活动的每种“水果类型”的列表一起加入。我正在使用 DATEPART 将日期分解为月和年,并尝试按此分组。我得到了结果(我无法分享),但数字与应有的数字完全不符。
  • LEFT OUTER JOIN dch_results ON (dch_results.Start_Date = daySpan.Date OR dch_results.End_Date IS NULL))
  • 什么知道...我回答了我自己的问题。

标签: sql-server date


【解决方案1】:

当您处理日期时,您可以利用这样一个事实,即实际上并没有那么多。单独跟踪它们是完全可行的。创建并填充涵盖您需要的整个范围的日期表,例如:

CREATE TABLE date_list ([date] date PRIMARY KEY NOT NULL);

INSERT date_list([date])  
SELECT TOP 1000  --This is a quick-and-dirty example
  DATEADD(day,ROW_NUMBER() OVER(ORDER BY(SELECT NULL))-1,'2013-01-01')
FROM master.dbo.spt_values;

然后用这个来辅助主查询:

WITH daily_tally AS (
  SELECT
    [date],
    [Type],
    SUM(Qty) AS [daily_Qty]
  FROM date_list
  INNER JOIN Results ON [date] BETWEEN [Start_Date] AND [End_Date]
  GROUP BY [date],[Type]
)
SELECT
  MONTH([date]) AS [month],
  YEAR([date]) AS [year],
  [Type],
  AVG([daily_Qty]) AS [avg_Qty]
FROM daily_tally
GROUP BY MONTH([date]),YEAR([date]),[Type]

【讨论】:

  • 我最终得出了与您相同的结论,但我会感谢您的解决。 :-)
【解决方案2】:

这就是我最终得到的结果......我们已经创建了一个函数,它在两个日期范围之间每天返回:

/****** Object:  UserDefinedFunction [dbo].[udf_SpanOfDates]    Script Date: 06/09/2014 16:35:52 ******/
SET ANSI_NULLS OFF
GO

SET QUOTED_IDENTIFIER ON
GO


CREATE FUNCTION [dbo].[udf_SpanOfDates]
(
    @StartDate Datetime,
    @EndDate DateTime
)

RETURNS
    @Dates TABLE
(
    Date DateTime
    , Month_MM integer
    , Quarter_QQ integer
    , Year_CCYY integer
)
AS
BEGIN
While @StartDate <= @EndDate
    begin
        insert @Dates 
        (   Date
            , Month_MM
            , Quarter_QQ
            , Year_CCYY
        ) 
        Values 
        (   @StartDate
            , DATEPART(M,@StartDate)
            , DATEPART(Q,@StartDate)
            , DATEPART(YYYY,@StartDate)
        )
        Set @StartDate = @StartDate + 1
    end

RETURN

END
GO

使用该功能,我能够做到这一点:

IF OBJECT_ID('tempdb..#LocalTempFruitTable', 'U') IS NOT NULL
    DROP TABLE #LocalTempFruitTable

CREATE TABLE #LocalTempFruitTable(
    Start_Date date,
    End_Date date,
    Type varchar(50),
    QTY int)

INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2013-12-16','2014-02-01','Apple','12')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-01-16','2014-06-01','Apple','8')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-01-16','2014-04-11','Banana','5')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-03-16','2014-04-16','Banana','7')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2014-02-16','2014-03-01','Orange','24')
INSERT INTO #LocalTempFruitTable (Start_Date,End_Date,Type,QTY) VALUES ('2013-02-24','2014-05-01','Orange','2')

SELECT Month_MM,YEAR_CCYY,Type,SUM(QTY)
FROM    dbo.udf_SpanOfDates('2013-12-01', '2014-04-01') sD
INNER JOIN #LocalTempFruitTable fruit
    ON (fruit.Start_Date <= sD.Date
            AND
            (fruit.END_DATE >= sD.Date
                OR fruit.End_Date IS NULL)
        )
GROUP BY MONTH_MM,YEAR_CCYY,Type
ORDER BY YEAR_CCYY,Month_MM,Type

结果:

MM  YEAR    Type    FruitDays
12  2013    Apple   192
12  2013    Orange  62
1   2014    Apple   500
1   2014    Banana  80
1   2014    Orange  62
2   2014    Apple   236
2   2014    Banana  140
2   2014    Orange  368
3   2014    Apple   248
3   2014    Banana  267
3   2014    Orange  86
4   2014    Apple   8
4   2014    Banana  12
4   2014    Orange  2

此时,计算平均值是学术性的,因为我已经有了月份和“水果日”。

【讨论】:

    【解决方案3】:
    DECLARE @MyTable TABLE
    (
     Start_Date DATETIME,
     End_Date DATETIME,
     Type VARCHAR(20),
     Qty DECIMAL(19,6)
    )
    
    INSERT INTO @MyTable
    ( Start_Date, End_Date, Type, Qty )
    VALUES
    ( '12/16/2013', '02/01/2014', 'Apple', 12),
    ( '01/16/2014', '06/01/2014', 'Apple', 8),
    ( '01/16/2014', '04/11/2014', 'Banana', 5),
    ( '03/16/2014', '04/16/2014', 'Banana', 7),
    ( '02/16/2014', '03/01/2014', 'Orange', 24),
    ( '02/24/2013', '05/01/2014', 'Orange', 2);
    
    DECLARE @MinDate DATETIME
    SELECT @MinDate = MIN(Start_Date) FROM @MyTable
    DECLARE @MaxDate DATETIME
    SELECT @MaxDate = MAX(End_Date) FROM @MyTable
    
    
    DECLARE @number_of_numbers INT = 100000;
    
    ;WITH
    a AS (SELECT 1 AS i UNION ALL SELECT 1),
    b AS (SELECT 1 AS i FROM a AS x, a AS y),
    c AS (SELECT 1 AS i FROM b AS x, b AS y),
    d AS (SELECT 1 AS i FROM c AS x, c AS y),
    e AS (SELECT 1 AS i FROM d AS x, d AS y),
    f AS (SELECT 1 AS i FROM e AS x, e AS y),
    numbers AS 
    (
        SELECT TOP(@number_of_numbers)
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS number
        FROM f
    )
    
    
    SELECT d.Type, MONTH(d.CheckDate), YEAR(d.CheckDate), AVG(D.Qty) FROM
    (
    SELECT c.CheckDate, m.Type, SUM(m.Qty) Qty FROM
    (
        SELECT DATEADD(DAY, n.number, '1/1/2000') AS CheckDate FROM numbers n
    ) C
    LEFT JOIN @MyTable m
        ON c.CheckDate >= m.Start_Date and c.CheckDate <= m.End_Date
    WHERE c.CheckDate >= @MinDate AND c.CheckDate <= @MaxDate
    GROUP BY c.CheckDate, m.Type
    ) AS d
    GROUP BY d.Type, MONTH(d.CheckDate), YEAR(d.CheckDate)
    

    【讨论】:

      猜你喜欢
      • 2019-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-06
      相关资源
      最近更新 更多