如何计算包含日期和时间的 运行 总数?
How Do I Calculate A Running Total That Factors Both Date and Time?
我被要求创建一个存储过程来显示可变日期范围内的租赁项目数量。我有一个具有以下架构的 table:
--Note that this is condensed, and in reality has proper constraints
--and more columns. Many dates from this table are tied to a single
--ContractDetail (separate table) by ContractDetailId.
CREATE TABLE RentalContractDates
(
RentalDateId INT IDENTITY(1,1) NOT NULL, --PK
ContractDetailId INT NOT NULL, --FK
RentalDate DATETIME NOT NULL,
Quantity DECIMAL(20,8) NULL
);
INSERT INTO RentalContractDates (ContractDetailId, RentalDate, Quantity)
VALUES (1, '04/01/2016 3:00 PM', 10),
(1, '04/10/2016 1:00 PM', 2),
(1, '04/15/2016 11:00 AM', -5),
(1, '04/15/2016 11:30 AM', -2),
(1, '04/27/2016 2:00 PM', -5);
用户将输入一个日期范围进行搜索,该过程应找到该范围内的所有日期,然后还将截止时间考虑在内,以便客户在另一天支付租金。
示例场景: 全局截止时间设置为 12:00PM。我在 2016 年 4 月 1 日在 3:00PM 租了 10 个小部件。这基本上我的意思是我实际上是在 4/02/2016 租的,因为它已经过了 04/01 的截止时间。我在 2016 年 4 月 10 日以 1:00PM 的价格再租了 2 间,基本上是 2016 年 4 月 11 日。我在 2016 年 4 月 15 日 11:00 上午 return 5 个小部件,11:30 上午 11:30 还有 2 个小部件。我想在 2016 年 4 月 27 日 return 所有小部件,但我已经过了 12:00PM 的截止时间,所以我不会为 4/02-4/27 收费,我实际上打算4/02-4/28收费。
重要提示:如果我在 04 年 1 月之前租用了数量,这是报告范围的开始,我需要将这些包括在报告中。例如,如果我在 3 月 31 日有 12 笔租金,那么 4 月 1 日及以后的租金总额就会增加 12 笔。换句话说,任何先前的数量都需要计算为使用输入的报告@BeginDate 和@EndDate 参数引入的总和。
所以 04/01 会读作 12,04/02 会读作 22,等等
正如你所看到的,我不需要用户每天输入他们的租金,我只是让他们为他们的租金设置一个开始日期和时间以及数量,下次他们输入 date/time 组合,它将被重新求和。
当前代码: 我想将此查询与整个月的日历日期列表结合起来,并相应地设置它们的数量。
DECLARE @BeginDate DATETIME = '04/01/2016',
@EndDate DATETIME = '04/28/2016';
DECLARE
@CutoffTime TIME = '12:00 PM';
SET @BeginDate = @BeginDate + @CutoffTime;
SET @EndDate = @EndDate + @CutoffTime;
SELECT gbd.ContractDetailId,
gbd.RentalDate,
gbd.Cutoff,
gbd.Quantity,
'Running Total' = SUM(Quantity) OVER (PARTITION BY ContractDetailId, RentalDate, Cutoff ORDER BY RentalDate)
FROM (
SELECT
r.ContractDetailId,
'RentalDate' = CONVERT(Date, RentalDate),
r2.Cutoff,
r.Quantity
FROM RentalContractDates r
INNER JOIN
(
SELECT
rcd.ContractDetailId,
'Cutoff' = CASE WHEN CONVERT(TIME, RentalDate) >= @CutoffTime THEN 'AFTER CUTOFF' ELSE 'BEFORE CUTOFF' END
FROM
RentalContractDates rcd
) r2
ON r2.ContractDetailId = r.ContractDetailId
WHERE
r.RentalDate Between @BeginDate and @EndDate
GROUP BY r.ContractDetailId, CONVERT(DATE, RentalDate), r2.Cutoff, Quantity
) gbd
ORDER BY RentalDate, Cutoff DESC
我想用这个 CTE 加入数据并为每个日期设置一个数量:
;WITH T([Date]) AS
(
SELECT @StartDate
UNION ALL
SELECT DATEADD(DAY,1,T.[Date]) FROM T WHERE T.[Date] < @EndDate
)
SELECT * FROM T
预期最终输出:
完成后,报表最终看起来像这样,尽管它会被旋转并在名称中包含星期几:
ContractDetailId RentalDate Quantity
----------------------------------------------------------------
1 04/01/2016 0 -- 0, because rentals were input after cutoff.
1 04/02/2016 10
1 04/03/2016 10 -- Continues until 4/10
1 04/10/2016 10
1 04/11/2016 12 -- Continues until 4/15
1 04/15/2016 5 -- I returned 5 and then 2, so this should sum since both were before the cutoff time.
-- Continues until 4/27.
1 04/27/2016 5 -- 5, because -5 was entered past cutoff on 4/27.
1 04/28/2016 0
我有旋转代码以及最终输出所必需的动态 sql 已经完成(如果需要,我可以 post 这个),但我不知道如何正确分组这些数据pre-cutoff/post-cutoff 并相应地更改日期。我应该如何处理这种情况?感谢任何 advice/help!
编辑 1: 修复了不正确的样本数据。
--Inputs for your function
DECLARE @BeginDate DATE = '04/01/2016',
@EndDate DATE = '04/28/2016',
@ContractDetailID INT = 1;
--Defined in the function
DECLARE @CutoffTime TIME = '12:00 PM';
DECLARE @PriorSum DECIMAL(20,8) = 0;
DECLARE @RowCount INT = DATEDIFF(dd,@BeginDate,@Enddate) +1;
--Get Any quantities before Begin Date
SELECT @PriorSum=COALESCE(SUM(rcd.Quantity),0)
from RentalContractDates rcd
WHERE CAST(CASE when CAST(rcd.RentalDate as TIME) > @CutoffTime THEN DATEADD(dd,1,rcd.RentalDate) ELSE rcd.RentalDate end as date) < @BeginDate
AND @ContractDetailID = rcd.ContractDetailId
--Create the Days for the report
;WITH RecursiveRowGenerator (Row#, Iteration) AS (
SELECT 1, 1
UNION ALL
SELECT Row# + Iteration, Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(@RowCount+1))
UNION ALL
SELECT Row# + (Iteration * 2), Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(@RowCount+1))
)
, SqrtNRows AS (
SELECT *
FROM RecursiveRowGenerator
UNION ALL
SELECT 0, 0
)
, Rowtbl as (
SELECT top (@RowCount+1) A.Row# * POWER(2,CEILING(LOG(SQRT(@RowCount+1))/LOG(2))) + B.Row# as RowNum
FROM SqrtNRows A, SqrtNRows B
ORDER BY A.Row#, B.Row#
)
,
DateTable as (
select top (@RowCount) DATEADD(dd,RowNum,@BeginDate) AS ReportDate
from Rowtbl
where RowNum <= @RowCount
)
,
--Merge the days for the report with the actual rental data
GBD AS
( SELECT
@ContractDetailID as ContractDetailID,
DT.ReportDate AS 'RentalDate',
CASE when CAST(rcd.RentalDate as TIME) > @CutoffTime THEN 'AFTER CUTOFF' ELSE 'BEFORE CUTOFF' END AS 'Cutoff',
COALESCE(rcd.Quantity,0) AS Quantity
FROM DateTable DT
LEFT JOIN RentalContractDates rcd on
DT.ReportDate = CAST( CASE when CAST(rcd.RentalDate as TIME) > @CutoffTime THEN DATEADD(dd,1,rcd.RentalDate) ELSE rcd.RentalDate END as DATE)
AND @ContractDetailID = rcd.ContractDetailId
WHERE DT.ReportDate Between @BeginDate and @EndDate
)
--Final Select
SELECT gbd1.ContractDetailId,
gbd1.RentalDate,
(select SUM(gbd2.Quantity) from GBD GBD2 where GBD1.rentaldate >= GBD2.RentalDate) + @PriorSum AS RunningTotal
FROM GBD gbd1
GROUP BY gbd1.ContractDetailId,gbd1.RentalDate
ORDER BY gbd1.RentalDate asc
编辑:
递归行生成器由 Aaron Friel 设计,是我用于生成行的解决方案 t-sql select get all Months within a range of years。它为所有日期生成行,因此如果该日期没有记录,我们可以左连接一个数量或 0。使用此生成器生成 2000 年到 2016 年的日期非常便宜。将所有数量和之前的总和与这些日期相结合是有点昂贵的。将它们聚合在一起,这样即使在 RentalContractDates 中没有记录的日期,你也有一个 运行 总数是昂贵的部分。
处理截止时间问题的部分是:
gbd.RentalDate = CAST(CASE WHEN CAST(rcd.RentalDate AS TIME) > @CutoffTime THEN DATEADD(dd, 1, rcd.RentalDate)
ELSE rcd.RentalDate END AS DATE)
它将 rentaldate 仅转换为时间,与 cutoffTime 进行比较,如果过去则添加一天,然后仅转换为日期。
我被要求创建一个存储过程来显示可变日期范围内的租赁项目数量。我有一个具有以下架构的 table:
--Note that this is condensed, and in reality has proper constraints
--and more columns. Many dates from this table are tied to a single
--ContractDetail (separate table) by ContractDetailId.
CREATE TABLE RentalContractDates
(
RentalDateId INT IDENTITY(1,1) NOT NULL, --PK
ContractDetailId INT NOT NULL, --FK
RentalDate DATETIME NOT NULL,
Quantity DECIMAL(20,8) NULL
);
INSERT INTO RentalContractDates (ContractDetailId, RentalDate, Quantity)
VALUES (1, '04/01/2016 3:00 PM', 10),
(1, '04/10/2016 1:00 PM', 2),
(1, '04/15/2016 11:00 AM', -5),
(1, '04/15/2016 11:30 AM', -2),
(1, '04/27/2016 2:00 PM', -5);
用户将输入一个日期范围进行搜索,该过程应找到该范围内的所有日期,然后还将截止时间考虑在内,以便客户在另一天支付租金。
示例场景: 全局截止时间设置为 12:00PM。我在 2016 年 4 月 1 日在 3:00PM 租了 10 个小部件。这基本上我的意思是我实际上是在 4/02/2016 租的,因为它已经过了 04/01 的截止时间。我在 2016 年 4 月 10 日以 1:00PM 的价格再租了 2 间,基本上是 2016 年 4 月 11 日。我在 2016 年 4 月 15 日 11:00 上午 return 5 个小部件,11:30 上午 11:30 还有 2 个小部件。我想在 2016 年 4 月 27 日 return 所有小部件,但我已经过了 12:00PM 的截止时间,所以我不会为 4/02-4/27 收费,我实际上打算4/02-4/28收费。
重要提示:如果我在 04 年 1 月之前租用了数量,这是报告范围的开始,我需要将这些包括在报告中。例如,如果我在 3 月 31 日有 12 笔租金,那么 4 月 1 日及以后的租金总额就会增加 12 笔。换句话说,任何先前的数量都需要计算为使用输入的报告@BeginDate 和@EndDate 参数引入的总和。 所以 04/01 会读作 12,04/02 会读作 22,等等
正如你所看到的,我不需要用户每天输入他们的租金,我只是让他们为他们的租金设置一个开始日期和时间以及数量,下次他们输入 date/time 组合,它将被重新求和。
当前代码: 我想将此查询与整个月的日历日期列表结合起来,并相应地设置它们的数量。
DECLARE @BeginDate DATETIME = '04/01/2016',
@EndDate DATETIME = '04/28/2016';
DECLARE
@CutoffTime TIME = '12:00 PM';
SET @BeginDate = @BeginDate + @CutoffTime;
SET @EndDate = @EndDate + @CutoffTime;
SELECT gbd.ContractDetailId,
gbd.RentalDate,
gbd.Cutoff,
gbd.Quantity,
'Running Total' = SUM(Quantity) OVER (PARTITION BY ContractDetailId, RentalDate, Cutoff ORDER BY RentalDate)
FROM (
SELECT
r.ContractDetailId,
'RentalDate' = CONVERT(Date, RentalDate),
r2.Cutoff,
r.Quantity
FROM RentalContractDates r
INNER JOIN
(
SELECT
rcd.ContractDetailId,
'Cutoff' = CASE WHEN CONVERT(TIME, RentalDate) >= @CutoffTime THEN 'AFTER CUTOFF' ELSE 'BEFORE CUTOFF' END
FROM
RentalContractDates rcd
) r2
ON r2.ContractDetailId = r.ContractDetailId
WHERE
r.RentalDate Between @BeginDate and @EndDate
GROUP BY r.ContractDetailId, CONVERT(DATE, RentalDate), r2.Cutoff, Quantity
) gbd
ORDER BY RentalDate, Cutoff DESC
我想用这个 CTE 加入数据并为每个日期设置一个数量:
;WITH T([Date]) AS
(
SELECT @StartDate
UNION ALL
SELECT DATEADD(DAY,1,T.[Date]) FROM T WHERE T.[Date] < @EndDate
)
SELECT * FROM T
预期最终输出: 完成后,报表最终看起来像这样,尽管它会被旋转并在名称中包含星期几:
ContractDetailId RentalDate Quantity
----------------------------------------------------------------
1 04/01/2016 0 -- 0, because rentals were input after cutoff.
1 04/02/2016 10
1 04/03/2016 10 -- Continues until 4/10
1 04/10/2016 10
1 04/11/2016 12 -- Continues until 4/15
1 04/15/2016 5 -- I returned 5 and then 2, so this should sum since both were before the cutoff time.
-- Continues until 4/27.
1 04/27/2016 5 -- 5, because -5 was entered past cutoff on 4/27.
1 04/28/2016 0
我有旋转代码以及最终输出所必需的动态 sql 已经完成(如果需要,我可以 post 这个),但我不知道如何正确分组这些数据pre-cutoff/post-cutoff 并相应地更改日期。我应该如何处理这种情况?感谢任何 advice/help!
编辑 1: 修复了不正确的样本数据。
--Inputs for your function
DECLARE @BeginDate DATE = '04/01/2016',
@EndDate DATE = '04/28/2016',
@ContractDetailID INT = 1;
--Defined in the function
DECLARE @CutoffTime TIME = '12:00 PM';
DECLARE @PriorSum DECIMAL(20,8) = 0;
DECLARE @RowCount INT = DATEDIFF(dd,@BeginDate,@Enddate) +1;
--Get Any quantities before Begin Date
SELECT @PriorSum=COALESCE(SUM(rcd.Quantity),0)
from RentalContractDates rcd
WHERE CAST(CASE when CAST(rcd.RentalDate as TIME) > @CutoffTime THEN DATEADD(dd,1,rcd.RentalDate) ELSE rcd.RentalDate end as date) < @BeginDate
AND @ContractDetailID = rcd.ContractDetailId
--Create the Days for the report
;WITH RecursiveRowGenerator (Row#, Iteration) AS (
SELECT 1, 1
UNION ALL
SELECT Row# + Iteration, Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(@RowCount+1))
UNION ALL
SELECT Row# + (Iteration * 2), Iteration * 2
FROM RecursiveRowGenerator
WHERE Iteration * 2 < CEILING(SQRT(@RowCount+1))
)
, SqrtNRows AS (
SELECT *
FROM RecursiveRowGenerator
UNION ALL
SELECT 0, 0
)
, Rowtbl as (
SELECT top (@RowCount+1) A.Row# * POWER(2,CEILING(LOG(SQRT(@RowCount+1))/LOG(2))) + B.Row# as RowNum
FROM SqrtNRows A, SqrtNRows B
ORDER BY A.Row#, B.Row#
)
,
DateTable as (
select top (@RowCount) DATEADD(dd,RowNum,@BeginDate) AS ReportDate
from Rowtbl
where RowNum <= @RowCount
)
,
--Merge the days for the report with the actual rental data
GBD AS
( SELECT
@ContractDetailID as ContractDetailID,
DT.ReportDate AS 'RentalDate',
CASE when CAST(rcd.RentalDate as TIME) > @CutoffTime THEN 'AFTER CUTOFF' ELSE 'BEFORE CUTOFF' END AS 'Cutoff',
COALESCE(rcd.Quantity,0) AS Quantity
FROM DateTable DT
LEFT JOIN RentalContractDates rcd on
DT.ReportDate = CAST( CASE when CAST(rcd.RentalDate as TIME) > @CutoffTime THEN DATEADD(dd,1,rcd.RentalDate) ELSE rcd.RentalDate END as DATE)
AND @ContractDetailID = rcd.ContractDetailId
WHERE DT.ReportDate Between @BeginDate and @EndDate
)
--Final Select
SELECT gbd1.ContractDetailId,
gbd1.RentalDate,
(select SUM(gbd2.Quantity) from GBD GBD2 where GBD1.rentaldate >= GBD2.RentalDate) + @PriorSum AS RunningTotal
FROM GBD gbd1
GROUP BY gbd1.ContractDetailId,gbd1.RentalDate
ORDER BY gbd1.RentalDate asc
编辑: 递归行生成器由 Aaron Friel 设计,是我用于生成行的解决方案 t-sql select get all Months within a range of years。它为所有日期生成行,因此如果该日期没有记录,我们可以左连接一个数量或 0。使用此生成器生成 2000 年到 2016 年的日期非常便宜。将所有数量和之前的总和与这些日期相结合是有点昂贵的。将它们聚合在一起,这样即使在 RentalContractDates 中没有记录的日期,你也有一个 运行 总数是昂贵的部分。
处理截止时间问题的部分是:
gbd.RentalDate = CAST(CASE WHEN CAST(rcd.RentalDate AS TIME) > @CutoffTime THEN DATEADD(dd, 1, rcd.RentalDate)
ELSE rcd.RentalDate END AS DATE)
它将 rentaldate 仅转换为时间,与 cutoffTime 进行比较,如果过去则添加一天,然后仅转换为日期。