2012-02-27 130 views
2

我有一個表存儲在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語句,使我能夠設置數據值通過引用其他行的字段並對其執行數據操作。

A必須要求是時間範圍必須代表一個時間序列中,兩個範圍不能跨越彼此。

+1

我不明白這個邏輯。你能更具體嗎? – Diego 2012-02-27 10:57:07

+2

我懷疑在這裏有很多邊界情況需要考慮 - 一個新行可以有一個完全相同的From/To作爲現有行,並將其全部替換?如果它完全覆蓋多行呢?我認爲它可以跨越兩個現有的範圍? – 2012-02-27 11:24:15

+0

日期範圍必須表示一個時間序列,兩個範圍不能相互跨越。 – AngeloBad 2012-02-27 11:27:13

回答

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 
+0

這是一個很好的解決方案,但我無法按照您的建議導致數據庫結構。 – AngeloBad 2012-02-28 07:34:36

+0

我已經添加了一個例子,說明如何使用我建議的結構來保持插入等方面的完整性,並意識到它基本上與保留'DateTo'字段時的方法相同,因此我不確定對我有多大優勢無論如何,建議。 – GarethD 2012-02-28 08:58:15

+0

這是我需要的,謝謝 – AngeloBad 2012-02-28 14:32:33

0

您可以使用光標每次來從表中的每一行和aftwerwards做必要的計算。

If NewDateFrom >= RowDateFrom and NewDateFrom <= RowDateTo ... 

檢查this article來看看如何做一個光標。

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單次掃描 - 但我只認爲這是值得做的,如果性能是至關重要的。

+0

當新的範圍完全覆蓋保存的範圍時,它無法正常工作。爲了說明這些情況,我改變了兩個「修改」SELECT(具有DATEADD的SELECT)的條件,如下所示:'@NewFrom BETWEEN DATEADD(DAY,1,DateFrom)AND DateTo'表示將範圍結束和「對於移動開始的那個,@NewToDateWFEN DateFrom和DATEADD(DAY,-1,DateTo)''。 – 2012-02-27 18:31:07

+0

@AndriyM - 這正是我希望與OP建立的那種邊緣條件 - 這個查詢有什麼支持? – 2012-02-27 18:33:17

+0

對不起,我會+1你的解決方案,因爲我非常喜歡整個方法。 – 2012-02-27 18:34:21

相關問題