我有一个存储在SQL Server 2008上的表,它将值与日期范围相关联。
DateFrom DateTo Value
2012-01-01 2012-02-01 10
2012-02-02 2012-02-15 15
处理此表的应用程序,可以在存在之间插入新范围。 例如,如果我插入
DateFrom DateTo Value
2012-02-07 2012-02-10 12
结果必须是
DateFrom DateTo Value
2012-01-01 2012-02-01 10
2012-02-02 2012-02-06 15
2012-02-07 2012-02-10 12
2012-02-11 2012-02-15 15
我可以从应用程序以编程方式执行此操作,但我想知道是否有一些快速SQL语句使我能够通过引用其他行的字段并对其执行数据操作来设置数据值。
必须要求日期范围必须代表一个时间序列,两个范围不能相互跨越。
答案 0 :(得分:2)
我根据我在评论中给你的例子写了一个例子,它可能会做你想要的。一般而言,由于可能有多行要插入/删除,因此最好单独定义它们,然后使用MERGE
执行整体更改。
我还假设可以删除/插入以实现拆分 - 您无法从1更新并生成2行,因此您始终必须执行 插入,如果我同时做对,那么对称性会更清晰:
declare @T table (DateFrom datetime2, DateTo datetime2,Value int)
insert into @T(DateFrom , DateTo , Value) VALUES
('20120101', '20120201', 10),
('20120202', '20120206', 15),
('20120207', '20120210', 12),
('20120211', '20120215', 15)
select * from @t order by DateFrom
declare @NewFrom datetime2 = '20120205'
declare @NewTo datetime2 = '20120208'
declare @NewValue int = 8
--We need to identify a) rows to delete, b) new sliced rows to create, and c) the new row itself
;With AlteredRows as (
select @NewFrom as DateFrom,@NewTo as DateTo,@NewValue as Value,1 as toInsert
union all
select DateFrom,DATEADD(day,-1,@NewFrom),Value,1 from @t where @NewFrom between DATEADD(day,1,DateFrom) and DateTo
union all
select DATEADD(day,1,@NewTo),DateTo,Value,1 from @t where @NewTo between DateFrom and DATEADD(day,-1,DateTo)
union all
select DateFrom,DateTo,0,0 from @t where DateTo > @NewFrom and DateFrom < @NewTo
)
merge into @t t using AlteredRows ar on t.DateFrom = ar.DateFrom and t.DateTo = ar.DateTo
when matched and toInsert=0 then delete
when not matched then insert (DateFrom,DateTo,Value) values (ar.DateFrom,ar.DateTo,ar.Value);
select * from @t order by DateFrom
有可能重新编写CTE,使其成为@t
的单次扫描 - 但我认为如果性能至关重要,那么值得这样做。
答案 1 :(得分:1)
我过去遇到过类似的问题,并发现如果范围需要连续,最好的方法是取消范围的结束日期,并将其计算为下一个开始日期。然后,如果需要创建一个视图,如下所示:
SELECT FromDate,
( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM YourTable b
WHERE b.FromDate > a.FromDate
) [ToDate],
Value
FROM YourTable a
这确保了2个范围永远不会交叉,但是不一定确保在插入时不需要工作来获得所需的结果,但是它应该比存储开始和结束日期更易于维护并且具有更少的错误范围。
<强>附录强>
一旦我写下了以下所有内容,我意识到它并没有提高可维护性,以至于要远离DateTo
字段,它仍然需要相当数量的代码进行验证,但这就是我的意思无论如何要这样做。
DECLARE @T table (DateFrom DATE, Value INT)
INSERT INTO @T VALUES ('20120101', 10), ('20120202', 15), ('20120207', 12), ('20120211', 15)
DECLARE @NewFrom DATE = '20120209',
@NewTo DATE = '20120210',
@NewValue INT = 8
-- SHOW INITIAL VALUES FOR DEMONSTATIVE PURPOSES --
SELECT DateFrom,
ISNULL(( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM @t b
WHERE b.DateFrom > a.DateFrom
), CAST(GETDATE() AS DATE)) [DateTo],
Value
FROM @t a
ORDER BY DateFrom
;WITH CTE AS
( SELECT DateFrom,
( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM @t b
WHERE b.DateFrom > a.DateFrom
) [DateTo],
Value
FROM @t a
),
MergeCTE AS
( SELECT @NewFrom [DateFrom], @NewValue [Value], 'INSERT' [RowAction]
WHERE @NewFrom < @NewTo -- ENSURE A VALID RANGE IS ENTERED
UNION ALL
-- INSERT A ROW WHERE THE NEW DATE TO SLICES AN EXISTING PERIOD
SELECT DATEADD(DAY, 1, @NewTo), Value, 'INSERT'
FROM CTE
WHERE @NewTo BETWEEN DateFrom AND DateTo
UNION ALL
-- DELETE ALL ENTRIES STARTING WITHIN THE DEFINED PERIOD
SELECT DateFrom, Value, 'DELETE'
FROM CTE
WHERE DateFrom BETWEEN @NewFrom AND @NewTo
)
MERGE INTO @t t USING MergeCTE c ON t.DateFrom = c.DateFrom AND t.Value = c.Value
WHEN MATCHED AND RowAction = 'DELETE' THEN DELETE
WHEN NOT MATCHED THEN INSERT VALUES (c.DateFrom, c.Value);
SELECT DateFrom,
ISNULL(( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM @t b
WHERE b.DateFrom > a.DateFrom
), CAST(GETDATE() AS DATE)) [DateTo],
Value
FROM @t a
ORDER BY DateFrom
答案 2 :(得分:0)
您可以使用光标一次从表格中获取每一行,然后进行必要的计算。
If NewDateFrom >= RowDateFrom and NewDateFrom <= RowDateTo ...
检查this article以了解如何制作光标。