【问题标题】:How to get missing data from this result set?如何从此结果集中获取缺失的数据?
【发布时间】:2019-05-14 17:15:44
【问题描述】:

基于两个输入 - 日期(日历中的任何日期)和日期期间(年、月或周)结果应汇总数据:

请原谅我的文字很长,但是太短了没办法解释,

例如;当@period 指定为 YEAR 时,它应该根据当年的所有月份汇总结果,对于缺失的月份,它应该为 0,

当@period 指定为 MONTH 时,它应该根据该年特定月份的所有日期汇总结果,对于缺少的日期,它应该为 0,

当@period 指定为 WEEK 时,它应该根据该特定月份和年份的特定周中的天数(给定一周的第一天和最后一天之间)汇总结果,并且对于缺少的工作日,它应该是 0,

当@period 指定为 DAY 时,它应该根据给定日期的小时数(0 小时到 23 小时)聚合结果,对于缺失的小时数,它应该是 0。

  1. 当前代码仅提供表中存在数据的结果,我需要将缺失数据设为 0 表示月、工作日和小时

这里是示例数据:

CREATE TABLE [dbo].[transferTable](
    [ID] [bigint] IDENTITY(1000,1) NOT NULL,
    [transferDateTime] [datetime] NOT NULL,
    [transferAmount] [money] NOT NULL
) ON [PRIMARY]

GO
SET IDENTITY_INSERT [dbo].[transferTable] ON 

GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1000, CAST(0x0000AA2C0110897B AS DateTime), 10.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1001, CAST(0x0000AA2D00F0AA50 AS DateTime), 151.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1002, CAST(0x0000A8850110897B AS DateTime), 10.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1003, CAST(0x0000AA0E0121043B AS DateTime), 151.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1005, CAST(0x0000AA4B01220A00 AS DateTime), 70.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1006, CAST(0x0000AA4A013284C0 AS DateTime), 75.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1007, CAST(0x0000AA4401537A40 AS DateTime), 85.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1008, CAST(0x0000AA4F002AD8C0 AS DateTime), 20.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1009, CAST(0x0000AA4F003B5380 AS DateTime), 25.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1010, CAST(0x0000AA4F005C4900 AS DateTime), 45.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1011, CAST(0x0000AA4F005C4900 AS DateTime), 14.0000)
GO
INSERT [dbo].[transferTable] ([ID], [transferDateTime], [transferAmount]) VALUES (1012, CAST(0x0000A8C4006CC3C0 AS DateTime), 66.0000)
GO
SET IDENTITY_INSERT [dbo].[transferTable] OFF
GO

实际代码:


DECLARE @calenderDate DATETIME2(0) = '2019-04-16 05:00'
DECLARE @period varchar(10) = 'year'

DECLARE @year varchar(10) =  DATEPART(YEAR,@calenderDate) 
DECLARE @month varchar(10)=  DATEPART(MONTH,@calenderDate)
DECLARE @week varchar(10) =  DATEPART(WEEK,@calenderDate) 
DECLARE @hour varchar(10) =  DATEPART(HOUR,@calenderDate)

select  case when @period = 'YEAR' then DATEPART(MONTH,transferDateTime)
            when @period = 'MONTH' then DATEPART(DAY,transferDateTime)
            when @period = 'WEEK' then DATEPART(WEEK,transferDateTime)
            when @period = 'DAY' then DATEPART(HOUR,transferDateTime)
        end as period, 
     COUNT (t.transferAmount) as volOfTxns,
     SUM (t.transferAmount) as ValueOfTxns
from transferTable t
where  
 (
        (@period = 'YEAR'   AND DATEPART(YEAR,t.transferDateTime) = @year)
    OR  (@period = 'MONTH'  AND ( DATEPART(YEAR,t.transferDateTime) = @year AND DATEPART(MONTH,t.transferDateTime) = @month))
    OR  (@period = 'WEEK'   AND (DATEPART(YEAR,t.transferDateTime) = @year AND DATEPART(MONTH,t.transferDateTime) = @month) AND DATEPART(WEEK,t.transferDateTime) = @week)
    OR  (@period = 'DAY'   AND (DATEPART(YEAR,t.transferDateTime) = @year AND DATEPART(MONTH,t.transferDateTime) = @month) AND DATEPART(WEEK,t.transferDateTime) = @week AND DATEPART(HOUR,t.transferDateTime) = @hour)
    ) 


group by case when @period = 'YEAR' then DATEPART(MONTH,transferDateTime)
              when @period = 'MONTH' then DATEPART(DAY,transferDateTime)
              when @period = 'WEEK' then DATEPART(WEEK,transferDateTime)
              when @period = 'DAY' then DATEPART(HOUR,transferDateTime)
          end

问题 1 - 我希望丢失的数据应该显示为 0。

例如,如果输入如下所示;

DECLARE @calenderDate DATETIME2(0) = '2019-03-16 05:00'
DECLARE @period varchar(10) = 'year'

实际结果是:

period  volOfTxns   ValueOfTxns
3          1         151.00
4          2         161.00
5          7         334.00

预期结果是:同时显示该年缺少的月份,但期间字段中的值为 0(这里期间字段代表月份)。

period  volOfTxns   ValueOfTxns
1         0                 0
2         0                 0
3         1                 151
4         2                 161
5         7                 334
6         0                 0
7         0                 0
8         0                 0
9         0                 0
10        0                 0
11        0                 0
12        0                 0

同样,如果 @period = 'month' 它应该在期间字段(这里period 字段代表日期)。

同样,如果 @period = 'week' 它应该在周期字段(这里的周期字段代表工作日)中显示周日到周六的工作日(对于缺少的工作日,它应该显示 0)。

同样,如果 @period = 'day' 它应该在句点字段(这里句点字段代表小时)中显示小时 00 到 23 小时(对于缺少的小时,它应该显示 0)。

【问题讨论】:

  • 这个查询肯定会受到kitchen sink 的影响,但这些参数也不是 SARGable,这使得该查询成为性能的噩梦。为什么在这里需要一个适合所有查询的尺寸?多个查询会为您提供更好的服务。

标签: sql-server tsql group-by sql-server-2012 common-table-expression


【解决方案1】:

使用 rangeAB(下面的 T-SQL 代码)很容易。

将您的查询变成子查询。使用 rangeAB 生成您需要的数字(例如,选择 YEAR 时为 1-12)。然后将您的子查询左连接到 rangeAB 查询。

SELECT 
  [period]    = r.RN,
  volOfTxns   = ISNULL(t.volOfTxns,0),
  ValueOfTxns = ISNULL(t.ValueOfTxns,0)
FROM dbo.rangeAB(1,12,1,1) AS r
LEFT JOIN
(
  select  case when @period = 'YEAR' then DATEPART(MONTH,transferDateTime)
              when @period = 'MONTH' then DATEPART(DAY,transferDateTime)
              when @period = 'WEEK' then DATEPART(WEEK,transferDateTime)
              when @period = 'DAY' then DATEPART(HOUR,transferDateTime)
          end as period, 
       COUNT (t.transferAmount) as volOfTxns,
       SUM (t.transferAmount) as ValueOfTxns
  from      transferTable AS t
  where  
   (
          (@period = 'YEAR'   AND DATEPART(YEAR,t.transferDateTime) = @year)
      OR  (@period = 'MONTH'  AND ( DATEPART(YEAR,t.transferDateTime) = @year AND DATEPART(MONTH,t.transferDateTime) = @month))
      OR  (@period = 'WEEK'   AND (DATEPART(YEAR,t.transferDateTime) = @year AND DATEPART(MONTH,t.transferDateTime) = @month) AND DATEPART(WEEK,t.transferDateTime) = @week)
      OR  (@period = 'DAY'   AND (DATEPART(YEAR,t.transferDateTime) = @year AND DATEPART(MONTH,t.transferDateTime) = @month) AND DATEPART(WEEK,t.transferDateTime) = @week AND DATEPART(HOUR,t.transferDateTime) = @hour)
      ) 


  group by case when @period = 'YEAR' then DATEPART(MONTH,transferDateTime)
                when @period = 'MONTH' then DATEPART(DAY,transferDateTime)
                when @period = 'WEEK' then DATEPART(WEEK,transferDateTime)
                when @period = 'DAY' then DATEPART(HOUR,transferDateTime)
            END
) AS t ON r.RN = t.[period];

返回:

period               volOfTxns   ValueOfTxns
-------------------- ----------- ---------------------
1                    0           0.00
2                    0           0.00
3                    1           151.00
4                    2           161.00
5                    7           334.00
6                    0           0.00
7                    0           0.00
8                    0           0.00
9                    0           0.00
10                   0           0.00
11                   0           0.00
12                   0           0.00

要处理多个句点,您可以将 rangeAB(1,12,1,1) 中的数字 12 更改为表达式/Case 语句,其中较大的数字基于您用于其他类型句点的任何逻辑。

RangeAB 代码:

CREATE FUNCTION dbo.rangeAB
(
  @low  BIGINT, 
  @high BIGINT, 
  @gap  BIGINT,
  @row1 BIT
)
/****************************************************************************************
[Purpose]:
 Creates up to 531,441,000,000 sequentia1 integers numbers beginning with @low and ending 
 with @high. Used to replace iterative methods such as loops, cursors and recursive CTEs 
 to solve SQL problems. Based on Itzik Ben-Gan's getnums function with some tweeks and 
 enhancements and added functionality. The logic for getting rn to begin at 0 or 1 is 
 based comes from Jeff Moden's fnTally function. 

 The name range because it's similar to clojure's range function. The name "rangeAB" as 
 used because "range" is a reserved SQL keyword.

[Author]: Alan Burstein

[Compatibility]: 
 SQL Server 2008+ and Azure SQL Database

[Syntax]:
 SELECT r.RN, r.OP, r.N1, r.N2
 FROM dbo.rangeAB(@low,@high,@gap,@row1) AS r;

[Parameters]:
 @low  = a bigint that represents the lowest value for n1.
 @high = a bigint that represents the highest value for n1.
 @gap  = a bigint that represents how much n1 and n2 will increase each row; @gap also
         represents the difference between n1 and n2.
 @row1 = a bit that represents the first value of rn. When @row = 0 then rn begins
         at 0, when @row = 1 then rn will begin at 1.

[Returns]:
 Inline Table Valued Function returns:
 rn = bigint; a row number that works just like T-SQL ROW_NUMBER() except that it can 
      start at 0 or 1 which is dictated by @row1.
 op = bigint; returns the "opposite number that relates to rn. When rn begins with 0 and
      ends with 10 then 10 is the opposite of 0, 9 the opposite of 1, etc. When rn begins
      with 1 and ends with 5 then 1 is the opposite of 5, 2 the opposite of 4, etc...
 n1 = bigint; a sequential number starting at the value of @low and incrimentingby the
      value of @gap until it is less than or equal to the value of @high.
 n2 = bigint; a sequential number starting at the value of @low+@gap and  incrimenting 
      by the value of @gap.

[Dependencies]:
N/A

[Developer Notes]:

 1. The lowest and highest possible numbers returned are whatever is allowable by a 
    bigint. The function, however, returns no more than 531,441,000,000 rows (8100^3). 
 2. @gap does not affect rn, rn will begin at @row1 and increase by 1 until the last row
    unless its used in a query where a filter is applied to rn.
 3. @gap must be greater than 0 or the function will not return any rows.
 4. Keep in mind that when @row1 is 0 then the highest row-number will be the number of
    rows returned minus 1
 5. If you only need is a sequential set beginning at 0 or 1 then, for best performance
    use the RN column. Use N1 and/or N2 when you need to begin your sequence at any 
    number other than 0 or 1 or if you need a gap between your sequence of numbers. 
 6. Although @gap is a bigint it must be a positive integer or the function will
    not return any rows.
 7. The function will not return any rows when one of the following conditions are true:
      * any of the input parameters are NULL
      * @high is less than @low 
      * @gap is not greater than 0
    To force the function to return all NULLs instead of not returning anything you can
    add the following code to the end of the query:

      UNION ALL 
      SELECT NULL, NULL, NULL, NULL
      WHERE NOT (@high&@low&@gap&@row1 IS NOT NULL AND @high >= @low AND @gap > 0)

    This code was excluded as it adds a ~5% performance penalty.
 8. There is no performance penalty for sorting by rn ASC; there is a large performance 
    penalty for sorting in descending order WHEN @row1 = 1; WHEN @row1 = 0
    If you need a descending sort the use op in place of rn then sort by rn ASC. 

Best Practices:
--===== 1. Using RN (rownumber)
 -- (1.1) The best way to get the numbers 1,2,3...@high (e.g. 1 to 5):
 SELECT RN FROM dbo.rangeAB(1,5,1,1);
 -- (1.2) The best way to get the numbers 0,1,2...@high-1 (e.g. 0 to 5):
 SELECT RN FROM dbo.rangeAB(0,5,1,0);

--===== 2. Using OP for descending sorts without a performance penalty
 -- (2.1) The best way to get the numbers 5,4,3...@high (e.g. 5 to 1):
 SELECT op FROM dbo.rangeAB(1,5,1,1) ORDER BY rn ASC;
 -- (2.2) The best way to get the numbers 0,1,2...@high-1 (e.g. 5 to 0):
 SELECT op FROM dbo.rangeAB(1,6,1,0) ORDER BY rn ASC;

--===== 3. Using N1
 -- (3.1) To begin with numbers other than 0 or 1 use N1 (e.g. -3 to 3):
 SELECT N1 FROM dbo.rangeAB(-3,3,1,1);
 -- (3.2) ROW_NUMBER() is built in. If you want a ROW_NUMBER() include RN:
 SELECT RN, N1 FROM dbo.rangeAB(-3,3,1,1);
 -- (3.3) If you wanted a ROW_NUMBER() that started at 0 you would do this:
 SELECT RN, N1 FROM dbo.rangeAB(-3,3,1,0);

--===== 4. Using N2 and @gap
 -- (4.1) To get 0,10,20,30...100, set @low to 0, @high to 100 and @gap to 10:
 SELECT N1 FROM dbo.rangeAB(0,100,10,1);
 -- (4.2) Note that N2=N1+@gap; this allows you to create a sequence of ranges.
 --       For example, to get (0,10),(10,20),(20,30).... (90,100):
 SELECT N1, N2 FROM dbo.rangeAB(0,90,10,1);
 -- (4.3) Remember that a rownumber is included and it can begin at 0 or 1:
 SELECT RN, N1, N2 FROM dbo.rangeAB(0,90,10,1);

[Examples]:
--===== 1. Generating Sample data (using rangeAB to create "dummy rows")
 -- The query below will generate 10,000 ids and random numbers between 50,000 and 500,000
 SELECT
   someId    = r.rn,
   someNumer = ABS(CHECKSUM(NEWID())%450000)+50001 
 FROM rangeAB(1,10000,1,1) r;

--===== 2. Create a series of dates; rn is 0 to include the first date in the series
 DECLARE @startdate DATE = '20180101', @enddate DATE = '20180131';

 SELECT r.rn, calDate = DATEADD(dd, r.rn, @startdate)
 FROM dbo.rangeAB(1, DATEDIFF(dd,@startdate,@enddate),1,0) r;
 GO

--===== 3. Splitting (tokenizing) a string with fixed sized items
 -- given a delimited string of identifiers that are always 7 characters long
 DECLARE @string VARCHAR(1000) = 'A601225,B435223,G008081,R678567';

 SELECT
   itemNumber = r.rn, -- item's ordinal position 
   itemIndex  = r.n1, -- item's position in the string (it's CHARINDEX value)
   item       = SUBSTRING(@string, r.n1, 7) -- item (token)
 FROM dbo.rangeAB(1, LEN(@string), 8,1)  r;
 GO

--===== 4. Splitting (tokenizing) a string with random delimiters
 DECLARE @string VARCHAR(1000) = 'ABC123,999F,XX,9994443335';

 SELECT
   itemNumber = ROW_NUMBER() OVER (ORDER BY r.rn), -- item's ordinal position 
   itemIndex  = r.n1+1, -- item's position in the string (it's CHARINDEX value)
   item       = SUBSTRING
               (
                 @string,
                 r.n1+1,
                 ISNULL(NULLIF(CHARINDEX(',',@string,r.n1+1),0)-r.n1-1, 8000)
               ) -- item (token)
 FROM dbo.rangeAB(0,DATALENGTH(@string),1,1) r
 WHERE SUBSTRING(@string,r.n1,1) = ',' OR r.n1 = 0;
 -- logic borrowed from: http://www.sqlservercentral.com/articles/Tally+Table/72993/

--===== 5. Grouping by a weekly intervals
 -- 5.1. how to create a series of start/end dates between @startDate & @endDate
 DECLARE @startDate DATE = '1/1/2015', @endDate DATE = '2/1/2015';
 SELECT 
   WeekNbr   = r.RN,
   WeekStart = DATEADD(DAY,r.N1,@StartDate), 
   WeekEnd   = DATEADD(DAY,r.N2-1,@StartDate)
 FROM dbo.rangeAB(0,datediff(DAY,@StartDate,@EndDate),7,1) r;
 GO

 -- 5.2. LEFT JOIN to the weekly interval table
 BEGIN
  DECLARE @startDate datetime = '1/1/2015', @endDate datetime = '2/1/2015';
  -- sample data 
  DECLARE @loans TABLE (loID INT, lockDate DATE);
  INSERT @loans SELECT r.rn, DATEADD(dd, ABS(CHECKSUM(NEWID())%32), @startDate)
  FROM dbo.rangeAB(1,50,1,1) r;

  -- solution 
  SELECT 
    WeekNbr   = r.RN,
    WeekStart = dt.WeekStart, 
    WeekEnd   = dt.WeekEnd,
    total     = COUNT(l.lockDate)
  FROM dbo.rangeAB(0,datediff(DAY,@StartDate,@EndDate),7,1) r
  CROSS APPLY (VALUES (
    CAST(DATEADD(DAY,r.N1,@StartDate) AS DATE), 
    CAST(DATEADD(DAY,r.N2-1,@StartDate) AS DATE))) dt(WeekStart,WeekEnd)
  LEFT JOIN @loans l ON l.lockDate BETWEEN  dt.WeekStart AND dt.WeekEnd
  GROUP BY r.RN, dt.WeekStart, dt.WeekEnd ;
 END;

--===== 6. Identify the first vowel and last vowel in a along with their positions
 DECLARE @string VARCHAR(200) = 'This string has vowels';

 SELECT TOP(1) position = r.rn, letter = SUBSTRING(@string,r.rn,1)
 FROM dbo.rangeAB(1,LEN(@string),1,1) r
 WHERE SUBSTRING(@string,r.rn,1) LIKE '%[aeiou]%'
 ORDER BY r.rn;

 -- To avoid a sort in the execution plan we'll use op instead of rn
 SELECT TOP(1) position = r.op, letter = SUBSTRING(@string,r.op,1)
 FROM dbo.rangeAB(1,LEN(@string),1,1) r
 WHERE SUBSTRING(@string,r.rn,1) LIKE '%[aeiou]%'
 ORDER BY r.rn;

---------------------------------------------------------------------------------------
[Revision History]:
 Rev 00 - 20140518 - Initial Development - Alan Burstein
 Rev 01 - 20151029 - Added 65 rows to make L1=465; 465^3=100.5M. Updated comment section
                   - Alan Burstein
 Rev 02 - 20180613 - Complete re-design including opposite number column (op)
 Rev 03 - 20180920 - Added additional CROSS JOIN to L2 for 530B rows max - Alan Burstein
****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH L1(N) AS 
(
  SELECT 1
  FROM (VALUES
   (0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
   (0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
   (0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
   (0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
   (0),(0)) T(N) -- 90 values 
),
L2(N)  AS (SELECT 1 FROM L1 a CROSS JOIN L1 b CROSS JOIN L1 c),
iTally AS (SELECT rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)) FROM L2 a CROSS JOIN L2 b)
SELECT
  r.RN,
  r.OP,
  r.N1,
  r.N2
FROM
(
  SELECT
    RN = 0,
    OP = (@high-@low)/@gap,
    N1 = @low,
    N2 = @gap+@low
  WHERE @row1 = 0
  UNION ALL -- COALESCE required in the TOP statement below for error handling purposes
  SELECT TOP (ABS((COALESCE(@high,0)-COALESCE(@low,0))/COALESCE(@gap,0)+COALESCE(@row1,1)))
    RN = i.rn,
    OP = (@high-@low)/@gap+(2*@row1)-i.rn,
    N1 = (i.rn-@row1)*@gap+@low,
    N2 = (i.rn-(@row1-1))*@gap+@low
  FROM iTally AS i
  ORDER BY i.rn
) AS r
WHERE @high&@low&@gap&@row1 IS NOT NULL AND @high >= @low AND @gap > 0;
GO

【讨论】:

    【解决方案2】:

    我认为 Week 的处理不正确,并且会给出与您的查询预期的结果不同的结果,因此我将通过 CTE 构建周期,将其与您的数据连接起来并进行一些修改。

    我测试了所有时段 'YEAR'、'MONTH'、'WEEK'、'DAY' 并显示了结果。

    我的长查询(基于您的)如下:-

    DECLARE @calenderDate DATETIME2(0) = '2019-05-16 05:00'
    DECLARE @period varchar(10) = 'YEAR' -- 'MONTH' or 'WEEK''DAY'
    
    --find the start and end for each period
    Declare @PeriodStart datetime,@PeriodEnd datetime
    select @PeriodStart=case    when @period = 'YEAR' then cast(cast(year(@calenderDate) as varchar(4))+'-01-01' as datetime)
                                when @period = 'MONTH' then cast(cast(year(@calenderDate) as varchar(4))+'-'+cast(Month(@calenderDate) as varchar(2))+'-01' as datetime)
                                when @period = 'WEEK' then DATEADD(dd, -(DATEPART(dw, cast(@calenderDate as date))-1), cast(@calenderDate as date))
                                when @period = 'DAY' then cast(cast(year(@calenderDate) as varchar(4))+'-'+cast(month(@calenderDate) as varchar(2))+'-'+cast(day(@calenderDate)as varchar(2)) as datetime) end
    
    select @PeriodEnd=case      when @period = 'YEAR' then dateadd(MILLISECOND,-3,dateadd(month,12,@PeriodStart))
                                when @period = 'MONTH' then dateadd(MILLISECOND,-3,dateadd(month,1,@PeriodStart))
                                when @period = 'WEEK' then dateadd(MILLISECOND,-3,dateadd(day,7,@PeriodStart))
                                when @period = 'DAY' then dateadd(MILLISECOND,-3,dateadd(hour,24,@PeriodStart)) end
    
    ;with PeriodYear as (
        select @PeriodStart [Period]
        union all
        select dateadd(month,1,PeriodYear.[Period]) from PeriodYear where dateadd(month,1,PeriodYear.[Period])<=@PeriodEnd
    ),PeriodMonth as (
        select @PeriodStart [Period]
        union all
        select dateadd(DAY,1,PeriodMonth.[Period]) from PeriodMonth where dateadd(DAY,1,PeriodMonth.[Period])<=@PeriodEnd
    ),PeriodWeek as (
        select @PeriodStart [Period]
        union all
        select dateadd(DAY,1,PeriodWeek.[Period]) from PeriodWeek where dateadd(DAY,1,PeriodWeek.[Period])<=@PeriodEnd
    ),PeriodDay as (
        select @PeriodStart [Period]
        union all
        select dateadd(hour,1,PeriodDay.[Period]) from PeriodDay where dateadd(hour,1,PeriodDay.[Period])<=@PeriodEnd
    ),GroupData as (
        select case 
                when @period = 'YEAR'       then cast(cast(DATEPART(year,min(transferDateTime)) as varchar(4))+'-'+cast(DATEPART(MONTH,min(transferDateTime)) as varchar(2))+'-01' as datetime)
                when @period = 'MONTH'      then cast(cast(min(transferDateTime) as date) as datetime)
                when @period = 'WEEK'       then cast(cast(min(transferDateTime) as date) as datetime)
                when @period = 'DAY'        then dateadd(hour,DATEPART(HOUR,min(transferDateTime)),cast(cast(min(transferDateTime) as date) as datetime))
            end as [period], 
             COUNT (t.transferAmount) as volOfTxns,
             SUM (t.transferAmount) as ValueOfTxns
        from transferTable t
        where  
            t.transferDateTime between @PeriodStart and @PeriodEnd 
        group by case when @period = 'YEAR' then DATEPART(MONTH,transferDateTime)
                      when @period = 'MONTH' then DATEPART(DAY,transferDateTime)
                      when @period = 'WEEK' then DATEPART(DAY,transferDateTime)
                      when @period = 'DAY' then DATEPART(HOUR,transferDateTime)
                  end
    ),RangeDataSet as (
        select * from PeriodYear where @period = 'YEAR' union all 
        select * from PeriodMonth where @period = 'MONTH' union all 
        select * from PeriodWeek where @period = 'WEEK' union all 
        select * from PeriodDay where @period = 'DAY'
    )
    select RangeDataSet.[Period] [PeriodDate]
    ,case             when @period = 'YEAR'     then cast(DATEPART(MONTH,RangeDataSet.[Period])   as varchar(50))
                      when @period = 'MONTH'    then cast(DATEPART(DAY,RangeDataSet.[Period])     as varchar(50))
                      when @period = 'WEEK'     then cast(datename(WEEKDAY,RangeDataSet.[Period]) as varchar(50))
                      when @period = 'DAY'      then cast(Right('00' + cast(DATEPART(HOUR,RangeDataSet.[Period]) as varchar(2)),2) as varchar(50))+' hrs'
                  end [Period]
    ,isnull(volOfTxns,0) volOfTxns,isnull(ValueOfTxns,0) ValueOfTxns from RangeDataSet
    left outer join GroupData on GroupData.[Period]=RangeDataSet.[Period]
    

    每个周期类型的结果

    一年

    PeriodDate  Period  volOfTxns   ValueOfTxns
    2019-01-01 00:00:00.000 1   0   0.00
    2019-02-01 00:00:00.000 2   0   0.00
    2019-03-01 00:00:00.000 3   1   151.00
    2019-04-01 00:00:00.000 4   2   161.00
    2019-05-01 00:00:00.000 5   7   334.00
    2019-06-01 00:00:00.000 6   0   0.00
    2019-07-01 00:00:00.000 7   0   0.00
    2019-08-01 00:00:00.000 8   0   0.00
    2019-09-01 00:00:00.000 9   0   0.00
    2019-10-01 00:00:00.000 10  0   0.00
    2019-11-01 00:00:00.000 11  0   0.00
    2019-12-01 00:00:00.000 12  0   0.00
    

    月份

    PeriodDate  Period  volOfTxns   ValueOfTxns
    2019-05-01 00:00:00.000 1   0   0.00
    2019-05-02 00:00:00.000 2   0   0.00
    2019-05-03 00:00:00.000 3   0   0.00
    2019-05-04 00:00:00.000 4   0   0.00
    2019-05-05 00:00:00.000 5   1   85.00
    2019-05-06 00:00:00.000 6   0   0.00
    2019-05-07 00:00:00.000 7   0   0.00
    2019-05-08 00:00:00.000 8   0   0.00
    2019-05-09 00:00:00.000 9   0   0.00
    2019-05-10 00:00:00.000 10  0   0.00
    2019-05-11 00:00:00.000 11  1   75.00
    2019-05-12 00:00:00.000 12  1   70.00
    2019-05-13 00:00:00.000 13  0   0.00
    2019-05-14 00:00:00.000 14  0   0.00
    2019-05-15 00:00:00.000 15  0   0.00
    2019-05-16 00:00:00.000 16  4   104.00
    2019-05-17 00:00:00.000 17  0   0.00
    2019-05-18 00:00:00.000 18  0   0.00
    2019-05-19 00:00:00.000 19  0   0.00
    2019-05-20 00:00:00.000 20  0   0.00
    2019-05-21 00:00:00.000 21  0   0.00
    2019-05-22 00:00:00.000 22  0   0.00
    2019-05-23 00:00:00.000 23  0   0.00
    2019-05-24 00:00:00.000 24  0   0.00
    2019-05-25 00:00:00.000 25  0   0.00
    2019-05-26 00:00:00.000 26  0   0.00
    2019-05-27 00:00:00.000 27  0   0.00
    2019-05-28 00:00:00.000 28  0   0.00
    2019-05-29 00:00:00.000 29  0   0.00
    2019-05-30 00:00:00.000 30  0   0.00
    2019-05-31 00:00:00.000 31  0   0.00
    

    对于显示 7 天的周

    PeriodDate  Period                 volOfTxns    ValueOfTxns
    2019-05-12 00:00:00.000 Sunday     1             70.00
    2019-05-13 00:00:00.000 Monday     0             0.00
    2019-05-14 00:00:00.000 Tuesday    0             0.00
    2019-05-15 00:00:00.000 Wednesday   0            0.00
    2019-05-16 00:00:00.000 Thursday    4            104.00
    2019-05-17 00:00:00.000 Friday     0             0.00
    2019-05-18 00:00:00.000 Saturday    0            0.00
    

    一天

     PeriodDate Period  volOfTxns   ValueOfTxns
    2019-05-16 00:00:00.000 00 hrs  0   0.00
    2019-05-16 01:00:00.000 01 hrs  0   0.00
    2019-05-16 02:00:00.000 02 hrs  1   20.00
    2019-05-16 03:00:00.000 03 hrs  1   25.00
    2019-05-16 04:00:00.000 04 hrs  0   0.00
    2019-05-16 05:00:00.000 05 hrs  2   59.00
    2019-05-16 06:00:00.000 06 hrs  0   0.00
    2019-05-16 07:00:00.000 07 hrs  0   0.00
    2019-05-16 08:00:00.000 08 hrs  0   0.00
    2019-05-16 09:00:00.000 09 hrs  0   0.00
    2019-05-16 10:00:00.000 10 hrs  0   0.00
    2019-05-16 11:00:00.000 11 hrs  0   0.00
    2019-05-16 12:00:00.000 12 hrs  0   0.00
    2019-05-16 13:00:00.000 13 hrs  0   0.00
    2019-05-16 14:00:00.000 14 hrs  0   0.00
    2019-05-16 15:00:00.000 15 hrs  0   0.00
    2019-05-16 16:00:00.000 16 hrs  0   0.00
    2019-05-16 17:00:00.000 17 hrs  0   0.00
    2019-05-16 18:00:00.000 18 hrs  0   0.00
    2019-05-16 19:00:00.000 19 hrs  0   0.00
    2019-05-16 20:00:00.000 20 hrs  0   0.00
    2019-05-16 21:00:00.000 21 hrs  0   0.00
    2019-05-16 22:00:00.000 22 hrs  0   0.00
    2019-05-16 23:00:00.000 23 hrs  0   0.00
    

    希望它会有所帮助。

    【讨论】:

      【解决方案3】:

      以下使用递归 CTE。我只准备了年月的脚本。如果这对您有用,只需添加附加条件:

      DECLARE @calenderDate DATETIME2(0) = '2019-03-16 05:00'
      Declare @day1Month Date = Cast(Year(@calenderDate) As Char(4)) + '-' + Cast(Month(@calenderDate) As Char(2)) + '-01'
      Declare @Year Int = Year(@calenderDate)
      Declare @Month Int = Month(@calenderDate)
      
      DECLARE @period varchar(10) = 'Year' 
      
      Declare @periodlst Table (period Int)
      

      -------------------------------------------> 获取期间列表

      If @period = 'Year'  --List numbers 1 to 12
                Insert @periodlst Select * From ( Values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12) ) p(period);
      Else If @period = 'Month'  --List numbers 1 to end of month
             with numcte AS  
             (  
               SELECT 1 [num]  
               UNION all  
               SELECT [num] + 1 FROM numcte WHERE [num] < (Select  datediff(day, @day1Month, dateadd(month, 1, @day1Month)))
             )        
             Insert @periodlst Select * FROM numcte
      

      ----------------------------------- ---> 递归 CTE

      ;With cte As
      (
      Select 
             Case When @period = 'Year' Then Month(transferDateTime) 
                  When @period = 'Month' Then Day(transferDateTime)
              End As period    
             ,* 
      From transferTable Where 
             (@period = 'Year' And Year(transferDateTime) = @Year) Or
             (@period = 'Month' And Year(transferDateTime) = @Year And Month(transferDateTime) = @Month) 
      )
      , cte1 As
      (
               Select * From @periodlst
      )
      Select 
           cte1.period As periods, 
           Count(TransferAmount) As volOfTxns,
           IsNull(Sum(TransferAmount),0) As valueOfTxns
      From cte1 Left Join cte On
           cte.period = cte1.period
           Group By cte1.period
      

      【讨论】:

        【解决方案4】:

        这是解决同一问题的一种方法。不是将日期模式作为参数提交,然后根据传入的参数有条件地求和,汇总分组将允许您分层显示所有日期排列。这意味着年优先于月,月优先于日,依此类推。

        为了防止出现空值,我合并了日期部分,这也迫使我在 order by 子句中添加了一些技巧,以便日期数字以正确的方式出现。

        最后注意,@calendarday 参数用于提供 >= where 子句,这意味着此结果集将显示 5 月的交易,但您可以根据需要切换此设置,这意味着您可以全面了解数据在此之前。

        CREATE TABLE #transferTable(
            [ID] [bigint] IDENTITY(1000,1) NOT NULL,
            [transferDateTime] [datetime] NOT NULL,
            [transferAmount] [money] NOT NULL
        ) ON [PRIMARY]
        
        GO
        SET IDENTITY_INSERT #transfertable ON 
        
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1000, CAST(0x0000AA2C0110897B AS DateTime), 10.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1001, CAST(0x0000AA2D00F0AA50 AS DateTime), 151.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1002, CAST(0x0000A8850110897B AS DateTime), 10.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1003, CAST(0x0000AA0E0121043B AS DateTime), 151.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1005, CAST(0x0000AA4B01220A00 AS DateTime), 70.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1006, CAST(0x0000AA4A013284C0 AS DateTime), 75.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1007, CAST(0x0000AA4401537A40 AS DateTime), 85.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1008, CAST(0x0000AA4F002AD8C0 AS DateTime), 20.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1009, CAST(0x0000AA4F003B5380 AS DateTime), 25.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1010, CAST(0x0000AA4F005C4900 AS DateTime), 45.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1011, CAST(0x0000AA4F005C4900 AS DateTime), 14.0000)
        GO
        INSERT #transfertable ([ID], [transferDateTime], [transferAmount]) VALUES (1012, CAST(0x0000A8C4006CC3C0 AS DateTime), 66.0000)
        GO
        SET IDENTITY_INSERT #transfertable OFF
        GO
        
        DECLARE @calenderDate DATETIME2(0) = '2019-04-16 05:00'
        
        DECLARE @year varchar(10) =  DATEPART(YEAR,@calenderDate) 
        DECLARE @month varchar(10)=  DATEPART(MONTH,@calenderDate)
        DECLARE @week varchar(10) =  DATEPART(WEEK,@calenderDate) 
        DECLARE @hour varchar(10) =  DATEPART(HOUR,@calenderDate)
        
        select coalesce(cast(DATEPART(YEAR,transferDateTime)as nvarchar(16)),'Year total') as year_period, 
               coalesce(cast(DATEPART(MONTH,transferDateTime) as nvarchar(16)),'Month total') as month_period, 
               coalesce(cast(DATEPART(WEEK,transferDateTime)as nvarchar(16)),'week total')  as week_period,
               coalesce(cast(DATEPART(DAY,transferDateTime) as nvarchar(16)),'day total') as day_period, 
               count(t.transferAmount) as volofTxns, coalesce(sum(t.transferAmount),0) as ValueofTxns
        from #transferTable t where t.transferDateTime>=@calenderDate
        group by rollup (DATEPART(YEAR,transferDateTime), DATEPART(MONTH,transferDateTime), 
                    DATEPART(WEEK,transferDateTime),DATEPART(DAY,transferDateTime))
        order by year_period, month_period, week_period, 
                       case when datepart(day,transferdatetime) = '1' then 1 when datepart(day,transferdatetime) = '2' then 2 when datepart(day,transferdatetime) = '3' then 3 when datepart(day,transferdatetime) = '4' then 4 when datepart(day,transferdatetime) = '5' then 5 when datepart(day,transferdatetime) = '6' then 6 
                            when datepart(day,transferdatetime) = '7' then 7 when datepart(day,transferdatetime) = '8' then 8 when datepart(day,transferdatetime) = '9' then 9 when datepart(day,transferdatetime) = '10' then 10 when datepart(day,transferdatetime) = '11' then 11 when datepart(day,transferdatetime) = '12' then 12 
                            when datepart(day,transferdatetime) = '13' then 13 when datepart(day,transferdatetime) = '14' then 14 when datepart(day,transferdatetime) = '15' then 15 when datepart(day,transferdatetime) = '16' then 16 when datepart(day,transferdatetime) = '17' then 17 when datepart(day,transferdatetime) = '18' then 18 
                            when datepart(day,transferdatetime) = '19' then 19 when datepart(day,transferdatetime) = '20' then 20 when datepart(day,transferdatetime) = '21' then 21 when datepart(day,transferdatetime) = '22' then 22 when datepart(day,transferdatetime) = '23' then 23 when datepart(day,transferdatetime) = '24' then 24 
                            when datepart(day,transferdatetime) = '25' then 25 when datepart(day,transferdatetime) = '26' then 26 when datepart(day,transferdatetime) = '27' then 27 when datepart(day,transferdatetime) = '28' then 28 when datepart(day,transferdatetime) = '29' then 29 when datepart(day,transferdatetime) = '30' then 30 
                            when datepart(day,transferdatetime) = '31' then 31 else 32 end;
        
        drop table #transferTable;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-10-07
          • 2021-08-25
          • 2023-03-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多