标题听起来令人困惑,但请让我解释一下:如何使用From/To date列从单个记录中获取派生'N'个日期行?
我有一个表格,它有两列提供日期范围,一列提供值。我需要查询该表和“细节”的数据,如该
是否有可能只使用TSQL吗?
附加信息
问题的表是关于2-3million记录长而且还在不断增长
标题听起来令人困惑,但请让我解释一下:如何使用From/To date列从单个记录中获取派生'N'个日期行?
我有一个表格,它有两列提供日期范围,一列提供值。我需要查询该表和“细节”的数据,如该
是否有可能只使用TSQL吗?
附加信息
问题的表是关于2-3million记录长而且还在不断增长
假设日期的范围是相当窄的,一个替代方案是使用一个递归CTE来创建一个li在范围内的所有日期的ST然后再加入插值到它:
WITH LastDay AS
(
SELECT MAX(Date_To) AS MaxDate
FROM MyTable
),
Days AS
(
SELECT MIN(Date_From) AS TheDate
FROM MyTable
UNION ALL
SELECT DATEADD(d, 1, TheDate) AS TheDate
FROM Days CROSS JOIN LastDay
WHERE TheDate <= LastDay.MaxDate
)
SELECT mt.Item_ID, mt.Cost_Of_Item, d.TheDate
FROM MyTable mt
INNER JOIN Days d
ON d.TheDate BETWEEN mt.Date_From AND mt.Date_To;
我还假设的那个日期从和日期来表示一个包含的范围(即包括两个边缘) - 在日期上使用包容性BETWEEN
是不常见的。
编辑
在递归CTE SQL Server中的默认MAXRECURSION
是100,这将限制在查询日期范围内100天的跨度。您可以将其调整为maximum of 32767。
此外,如果你要过滤只是一个小范围的大表的日期,你可以调整CTE限制范围内的天数:
WITH DateRange AS
(
SELECT CAST('2014-01-01' AS DATE) AS MinDate,
CAST('2014-02-16' AS DATE) AS MaxDate
),
Days AS
(
SELECT MinDate AS TheDate
FROM DateRange
UNION ALL
SELECT DATEADD(d, 1, TheDate) AS TheDate
FROM Days CROSS APPLY DateRange
WHERE TheDate <= DateRange.MaxDate
)
SELECT mt.Item_ID, mt.Cost_Of_Item, d.TheDate
FROM MyTable mt
INNER JOIN Days d
ON d.TheDate BETWEEN mt.Date_From AND mt.Date_To
OPTION (MAXRECURSION 0);
您可以生成一个增量表,并将其加入到你的日期从:
查询:
With inc(n) as (
Select ROW_NUMBER() over (order by (select 1)) -1 From (
Select 1 From (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x1(n)
Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x2(n)
) as x(n)
)
Select item_id, cost, DATEADD(day, n, dateFrom), n From @dates d
Inner Join inc i on n <= DATEDIFF(day, dateFrom, dateTo)
Order by item_id
输出:
item_id cost Date n
1 100 2014-01-01 00:00:00.000 0
1 100 2014-01-02 00:00:00.000 1
1 100 2014-01-03 00:00:00.000 2
2 105 2014-01-08 00:00:00.000 2
2 105 2014-01-07 00:00:00.000 1
2 105 2014-01-06 00:00:00.000 0
2 105 2014-01-09 00:00:00.000 3
3 102 2014-02-14 00:00:00.000 3
3 102 2014-02-15 00:00:00.000 4
3 102 2014-02-16 00:00:00.000 5
3 102 2014-02-11 00:00:00.000 0
3 102 2014-02-12 00:00:00.000 1
3 102 2014-02-13 00:00:00.000 2
示例数据:
declare @dates table(item_id int, cost int, dateFrom datetime, dateTo datetime);
insert into @dates(item_id, cost, dateFrom, dateTo) values
(1, 100, '20140101', '20140103')
, (2, 105, '20140106', '20140109')
, (3, 102, '20140211', '20140216');
这可以使用Cursors
来实现。
我模拟提供的测试数据,并创建另一个表名为“DesiredTable”来存储里面的数据,并创建了以下cusror这就实现正是你在找什么:
SET NOCOUNT ON;
DECLARE @ITEM_ID int, @COST_OF_ITEM Money,
@DATE_FROM date, @DATE_TO date;
DECLARE @DateDiff INT; -- holds number of days between from & to columns
DECLARE @counter INT = 0; -- for loop counter
PRINT '-------- Begin the Date Expanding Cursor --------';
-- defining the cursor target statement
DECLARE Date_Expanding_Cursor CURSOR FOR
SELECT [ITEM_ID]
,[COST_OF_ITEM]
,[DATE_FROM]
,[DATE_TO]
FROM [dbo].[OriginalTable]
-- openning the cursor
OPEN Date_Expanding_Cursor
-- fetching next row data into the declared variables
FETCH NEXT FROM Date_Expanding_Cursor
INTO @ITEM_ID, @COST_OF_ITEM, @DATE_FROM, @DATE_TO
-- if next row is found
WHILE @@FETCH_STATUS = 0
BEGIN
-- calculate the number of days in between the date columns
SELECT @DateDiff = DATEDIFF(day,@DATE_FROM,@DATE_TO)
-- reset the counter to 0 for the next loop
set @counter = 0;
WHILE @counter <= @DateDiff
BEGIN
-- inserting rows inside the new table
insert into DesiredTable
Values (@COST_OF_ITEM, DATEADD(day,@counter,@DATE_FROM))
set @counter = @counter +1
END
-- fetching next row
FETCH NEXT FROM Date_Expanding_Cursor
INTO @ITEM_ID, @COST_OF_ITEM, @DATE_FROM, @DATE_TO
END
-- cleanup code
CLOSE Date_Expanding_Cursor;
DEALLOCATE Date_Expanding_Cursor;
的代码从原始表中提取每一行,然后计算DATE_FROM
和DATE_TO
列之间的天数,然后使用此数字脚本将创建要插入新表DesiredTable
内的相同行。
试试看,让我知道结果。
它作为一种魅力,谢谢你。只有一个问题:所讨论的表大约需要2-3百万条记录(并且正在增长),所以使用游标真的非常耗费资源,而且需要很长时间才能完成。 – dhuesca
哦,对不起。我在创建此解决方案时不知道这些信息。原谅我 :) –
另一种方法是创建和维护日历表,其中包含所有日期多年(在我们的应用程序中,我们有30年左右的表,每年延长)。然后,你可以链接到日历:
select <whatever you need>, calendar.day
from <your tables> inner join calendar on calendar.day between <min date> and <max date>
这种方法允许包括日历表的附加信息(节假日等) - 有时是非常有帮助的。
谢谢你的回答,尝试了小提琴,工作得很好,在我的服务器上试过......没有那么多...忘了说这个表是2mill记录长,当执行代码时我得到了一个530错误。努力更好地了解您的查询,看看我能否修复它。 – dhuesca
看起来数据中的日期范围大于100天 - 我已经更新了一些更多的想法。 – StuartLC
感谢您的更新,它可以根据需要运行。 – dhuesca