使用单列日期分隔记录以共享日期范围
Seperate Records to share Date Range utilizing a single column of dates
我的 table 支出记录中有以下记录。我喜欢做的是根据仅使用 1 列日期的开始和结束服务日期将每条记录分解成单独的记录集。
DisbursementID ServiceProviderID Original CircuitID Beginning_Service_Date Ending_Service_Date Amount
-------------- ----------------- -------- ----------- ---------------------- ------------------- -----------
53562 673 0 1814 2015-12-01 2015-12-31 531
53563 673 0 1814 2015-11-01 2015-11-30 531
我的目标结果看起来像
DisbursementID ServiceProviderID Original CircuitID Date Range Amount
-------------- ----------------- -------- ----------- ---------- -------
53562 673 0 1814 2015-12-01 531
53562 673 0 1814 2015-12-02 531
53562 673 0 1814 2015-12-03 531
53562 673 0 1814 2015-12-04 531
53562 673 0 1814 2015-12-05 531
53563 673 0 1814 2015-11-01 531
53563 673 0 1814 2015-11-02 531
53563 673 0 1814 2015-11-03 531
53563 673 0 1814 2015-11-04 531
53563 673 0 1814 2015-11-05 531
相反,我的结果看起来像
DisbursementID ServiceProviderID Original CircuitID Date Range Amount
-------------- ----------------- -------- ----------- ---------- -------
53562 673 0 1814 2015-12-01 531
53563 673 0 1814 2015-11-01 531
53563 673 0 1814 2015-11-02 531
53563 673 0 1814 2015-11-03 531
53563 673 0 1814 2015-11-04 531
53563 673 0 1814 2015-11-05 531
以下是我找到的一段代码,但适合我的需要。它几乎解决了我的问题,但我不知道如何从我的第一条记录中包含范围日期块。我知道为什么会这样,但不知道如何正确修复它:
;With Dates as
(
Select DisbursementID, ServiceProviderID,Original,CircuitID
,Beginning_Service_Date as BeginDate, Ending_Service_Date as EndDate
,Amount From Disbursement
Union All
Select DisbursementID, ServiceProviderID, Original,CircuitID
,DATEADD(day,1,BeginDate) as CalenderDate, EndDate
,Amount
From Dates
Where DATEADD(day,1,BeginDate) <= EndDate
)
Select DisbursementID, ServiceProviderID,Original,CircuitID
,BeginDate as [Date Range], Amount from Dates
Order By CircuitID
Option (MAXRECURSION 366);
如果您没有或不能使用 Tally/Calendar Table,另一种方法是使用临时计数 table.
Declare @YourTable table (DisbursementID int, ServiceProviderID int, Original int, CircuitID int, Beginning_Service_Date date, Ending_Service_Date date, Amount int)
Insert Into @YourTable values
( 53562,673,0,1814,'2015-12-01','2015-12-31',531)
,( 53563,673,0,1814,'2015-11-01','2015-11-30',531)
;with cte1 as (
Select MinDate=min(Beginning_Service_Date)
,MaxDate=max(Ending_Service_Date)
From @YourTable )
,cte2 as (
Select Top (DateDiff(DD,(select MinDate from cte1),(select MaxDate from cte1))+1)
D = DateAdd(DD,-1+Row_Number() Over (Order By (Select null)),(select MinDate from cte1))
From master..spt_values A -- ,master..spt_values B -- If you need more than 6 years
)
Select A.DisbursementID
,A.ServiceProviderID
,A.Original
,A.CircuitID
,[Date Range] = B.D
,A.Amount
From @YourTable A
Join cte2 B on B.D between A.Beginning_Service_Date and A.Ending_Service_Date
使用递归 cte 是生成一系列日期的最糟糕的方法之一。 John Cappelletti 的回答是 much better 按需生成日期范围而不是使用递归 cte。
如果您要在 55,000 多行中使用它,并且您将 运行 这种操作不止一次,您最好只创建 Dates
或 Calendar
table.
对于只有 152kb 的内存,你可以在一个 table 中有 30 年的日期,你可以这样使用它:
/* dates table */
declare @fromdate date = '20000101';
declare @years int = 30;
/* 30 years, 19 used data pages ~152kb in memory, ~264kb on disk */
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
select top (datediff(day, @fromdate,dateadd(year,@years,@fromdate)))
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,@fromdate))
into dbo.Dates
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date];
create unique clustered index ix_dbo_Dates_date
on dbo.Dates([Date]);
并像这样使用 Dates
table:
select
t.DisbursementID
, t.ServiceProviderID
, t.Original
, t.CircuitID
, d.[date]
, t.Amount
from t
inner join dates d
--on d.date >= t.Beginning_Service_Date
--and d.date <= t.Ending_Service_Date
/* if you want to have the date range work when
Beginning_Service_Date and Ending_Service_Date are backwards
you could use between */
on d.date between t.Beginning_Service_Date
and t.Ending_Service_Date
rextester 演示:http://rextester.com/WNMJW41879
数字和日历table参考:
- Generate a set or sequence without loops - 3 - Aaron Bertrand
- The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
- Creating a Date Table/Dimension in SQL Server 2008 - David Stein
- Calendar Tables - Why You Need One - David Stein
- Creating a date dimension or calendar table in SQL Server - Aaron Bertrand
- TSQL Function to Determine Holidays in SQL Server - Tim Cullen
- F_TABLE_DATE - Michael Valentine Jones
我的 table 支出记录中有以下记录。我喜欢做的是根据仅使用 1 列日期的开始和结束服务日期将每条记录分解成单独的记录集。
DisbursementID ServiceProviderID Original CircuitID Beginning_Service_Date Ending_Service_Date Amount
-------------- ----------------- -------- ----------- ---------------------- ------------------- -----------
53562 673 0 1814 2015-12-01 2015-12-31 531
53563 673 0 1814 2015-11-01 2015-11-30 531
我的目标结果看起来像
DisbursementID ServiceProviderID Original CircuitID Date Range Amount
-------------- ----------------- -------- ----------- ---------- -------
53562 673 0 1814 2015-12-01 531
53562 673 0 1814 2015-12-02 531
53562 673 0 1814 2015-12-03 531
53562 673 0 1814 2015-12-04 531
53562 673 0 1814 2015-12-05 531
53563 673 0 1814 2015-11-01 531
53563 673 0 1814 2015-11-02 531
53563 673 0 1814 2015-11-03 531
53563 673 0 1814 2015-11-04 531
53563 673 0 1814 2015-11-05 531
相反,我的结果看起来像
DisbursementID ServiceProviderID Original CircuitID Date Range Amount
-------------- ----------------- -------- ----------- ---------- -------
53562 673 0 1814 2015-12-01 531
53563 673 0 1814 2015-11-01 531
53563 673 0 1814 2015-11-02 531
53563 673 0 1814 2015-11-03 531
53563 673 0 1814 2015-11-04 531
53563 673 0 1814 2015-11-05 531
以下是我找到的一段代码,但适合我的需要。它几乎解决了我的问题,但我不知道如何从我的第一条记录中包含范围日期块。我知道为什么会这样,但不知道如何正确修复它:
;With Dates as
(
Select DisbursementID, ServiceProviderID,Original,CircuitID
,Beginning_Service_Date as BeginDate, Ending_Service_Date as EndDate
,Amount From Disbursement
Union All
Select DisbursementID, ServiceProviderID, Original,CircuitID
,DATEADD(day,1,BeginDate) as CalenderDate, EndDate
,Amount
From Dates
Where DATEADD(day,1,BeginDate) <= EndDate
)
Select DisbursementID, ServiceProviderID,Original,CircuitID
,BeginDate as [Date Range], Amount from Dates
Order By CircuitID
Option (MAXRECURSION 366);
如果您没有或不能使用 Tally/Calendar Table,另一种方法是使用临时计数 table.
Declare @YourTable table (DisbursementID int, ServiceProviderID int, Original int, CircuitID int, Beginning_Service_Date date, Ending_Service_Date date, Amount int)
Insert Into @YourTable values
( 53562,673,0,1814,'2015-12-01','2015-12-31',531)
,( 53563,673,0,1814,'2015-11-01','2015-11-30',531)
;with cte1 as (
Select MinDate=min(Beginning_Service_Date)
,MaxDate=max(Ending_Service_Date)
From @YourTable )
,cte2 as (
Select Top (DateDiff(DD,(select MinDate from cte1),(select MaxDate from cte1))+1)
D = DateAdd(DD,-1+Row_Number() Over (Order By (Select null)),(select MinDate from cte1))
From master..spt_values A -- ,master..spt_values B -- If you need more than 6 years
)
Select A.DisbursementID
,A.ServiceProviderID
,A.Original
,A.CircuitID
,[Date Range] = B.D
,A.Amount
From @YourTable A
Join cte2 B on B.D between A.Beginning_Service_Date and A.Ending_Service_Date
使用递归 cte 是生成一系列日期的最糟糕的方法之一。 John Cappelletti 的回答是 much better 按需生成日期范围而不是使用递归 cte。
如果您要在 55,000 多行中使用它,并且您将 运行 这种操作不止一次,您最好只创建 Dates
或 Calendar
table.
对于只有 152kb 的内存,你可以在一个 table 中有 30 年的日期,你可以这样使用它:
/* dates table */
declare @fromdate date = '20000101';
declare @years int = 30;
/* 30 years, 19 used data pages ~152kb in memory, ~264kb on disk */
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
select top (datediff(day, @fromdate,dateadd(year,@years,@fromdate)))
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,@fromdate))
into dbo.Dates
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date];
create unique clustered index ix_dbo_Dates_date
on dbo.Dates([Date]);
并像这样使用 Dates
table:
select
t.DisbursementID
, t.ServiceProviderID
, t.Original
, t.CircuitID
, d.[date]
, t.Amount
from t
inner join dates d
--on d.date >= t.Beginning_Service_Date
--and d.date <= t.Ending_Service_Date
/* if you want to have the date range work when
Beginning_Service_Date and Ending_Service_Date are backwards
you could use between */
on d.date between t.Beginning_Service_Date
and t.Ending_Service_Date
rextester 演示:http://rextester.com/WNMJW41879
数字和日历table参考:
- Generate a set or sequence without loops - 3 - Aaron Bertrand
- The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
- Creating a Date Table/Dimension in SQL Server 2008 - David Stein
- Calendar Tables - Why You Need One - David Stein
- Creating a date dimension or calendar table in SQL Server - Aaron Bertrand
- TSQL Function to Determine Holidays in SQL Server - Tim Cullen
- F_TABLE_DATE - Michael Valentine Jones