2012-02-27 2 views
2

날짜 범위에 값을 연결하는 SQL Server 2008에 저장된 테이블이 있습니다.다른 행 필드 값을 기준으로 UPDATE 필드

DateFrom  DateTo   Value 
2012-01-01 2012-02-01  10 
2012-02-02 2012-02-15  15 

이 표를 처리하는 응용 프로그램은 존재 사이에 새로운 범위를 삽입 할 수 있습니다. 설정 나를 수 있도록 몇 가지 빠른 SQL 문이있는 경우 예를 들어, 난 결과는

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 

내가 응용 프로그램에서 프로그래밍 방식으로 그렇게 할 수 있어야합니다

DateFrom  DateTo   Value 
2012-02-07 2012-02-10  12 

를 삽입,하지만 난 궁금해하는 경우 다른 행의 필드를 참조하고 그것에 대한 데이터 연산을 수행하여 데이터 값.

날짜 범위는 시간 순서를 나타내야하며 두 범위는 서로 확장 될 수 없어야합니다.

+1

나는 논리를 이해하지 못했습니다. 좀 더 구체적으로 해주실 수 있습니까? – Diego

+2

여기서 고려해야 할 많은 경우가 있다고 생각합니다. 새 행이 From/To를 기존 행과 완전히 같게 할 수 있고 완전히 바꿀 수 있습니까? 여러 행을 완전히 커버한다면? 나는 그것이 기존의 두 범위에 걸쳐있을 수 있다고 가정합니까? –

+0

날짜 범위는 시간 순서를 나타내야하며, 두 범위는 서로 확장 될 수 없습니다. – AngeloBad

답변

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

+0

필자는 제안 된 구조로 인서트 등에서 무결성을 유지하는 방법에 대한 예제를 추가했으며 'DateTo'필드를 유지할 때 기본적으로 동일한 접근법을 사용하므로 실제로 많은 이점이 있음을 확신하지 못했습니다. 어쨌든 제안. – GarethD

+0

이것이 내가 필요로하는 것입니다. – AngeloBad

0

커서를 사용하여 한 번에 테이블에서 각 행을 가져와 나중에 필요한 계산을 수행 할 수 있습니다.

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 

그것은하는 것이 가능할 수있다는 @t의 단일 스캔의 있도록 CTE를 다시 쓰기 -하지만 난 단지 그것을하고 가치가 있다고 생각 성능이 중요한 경우 그.

+0

새로운 범위가 저장된 범위를 완전히 커버 할 때 올바르게 작동하지 않습니다. 이러한 경우를 설명하기 위해 두 가지 "수정"SELECT (DATEADD를 사용하는 SELECT)에 대한 조건을 다음과 같이 변경했습니다. 범위 끝을 이동하는 SELECT에 대해'@NewFrom BETWEEN DATEADD (DAY, 1, DateFrom) AND DateTo ' DateFrom과 DATEADD (DAY, -1, DateTo) 사이의 @NewTo는 시작 부분을 이동합니다. –

+0

@AndriyM - 그것이 OP와의 확립을 바라는 바로 그 종류의 가장자리 조건이었습니다 -이 쿼리는 무엇을 지원해야합니까? –

+0

죄송 합니다만, 전체 해결책을 매우 좋아했기 때문에 해결책을 +1 할 것입니다. –

관련 문제