2016-07-04 4 views
3

두 개의 간격 데이터 I.E.가 있습니다.간격 데이터 그룹 병합 -

Start End Type1 Type2 
0  2 L  NULL 
2  5 L  NULL 
5  7 L  NULL 
7  10 L  NULL 
2  3 NULL S 
3  5 NULL S 
5  8 NULL S 
11 12 NULL S 

내가하고 싶은 것은이 세트를 하나에 병합하는 것입니다. 이는 섬과 격차 솔루션을 이용하여 가능 보이지만 인해 간격의 비 연속 특성에 내가 그것을 적용하는 방법에 대한 이동하는 방법을 잘 모르겠어요 ... 내가 기대하고있어 출력은 다음과 같습니다

Start End Type1 Type2 
0  2 L  NULL 
2  3 L  S 
3  5 L  S 
5  7 L  S 
7  8 L  S 
8  10 L  NULL 
11 12 NULL S 

누군가가 전에 이런 식으로 뭔가를 한 ??? 감사!

아래 스크립트 작성 :

CREATE TABLE Table1 
    ([Start] int, [End] int, [Type1] varchar(4), [Type2] varchar(4)) 
; 

INSERT INTO Table1 
    ([Start], [End], [Type1], [Type2]) 
VALUES 
    (0, 2, 'L', NULL), 
    (2, 3, NULL, 'S'), 
    (2, 5, 'L', NULL), 
    (3, 5, NULL, 'S'), 
    (5, 7, 'L', NULL), 
    (5, 8, NULL, 'S'), 
    (7, 10, 'L', NULL), 
    (11, 12, NULL, 'S') 
; 
+0

는 그 ([간격 포장] http://blogs.solidq.com/en/sqlserver/packing- 보인다 간격 /)을 Itzik Ben-Gan이 사용할 수 있습니다. –

+0

링크는 좋지만 모든 SQL은 SQL 2008R2와 호환되지 않습니다 (우리는 FLOOR 등을 사용할 수 없습니다). – Harry

답변

1

내가 End 독점하고 주어진 간격이 중복되지 않는, Start이 포함 된 것으로 가정합니다.

CTE_Number은 숫자 표입니다. 여기에 즉시 생성됩니다. 내 데이터베이스에 영구 테이블로 가지고 있습니다.

CTE_T1CTE_T2은 숫자 표를 사용하여 각 간격을 해당 행 수로 확장합니다. Type1Type2 위해 예를 들어, 간격 [2,5)이 두번 수행 Values

2 
3 
4 

와 행을 생성한다.

에 대한 결과는 Value에 함께 FULL JOINed입니다.

마지막으로 갭 - 아일랜드 통과 그룹/붕괴 간격 뒤로.

단계별로 CTE-by-CTE 쿼리를 실행하고 중간 결과를 검사하여 작동 방식을 이해합니다.

샘플 데이터

는 I 값 사이의 갭이있을 때 한 경우를 설명하기 위해 몇 행을 추가했다.

DECLARE @Table1 TABLE 
    ([Start] int, [End] int, [Type1] varchar(4), [Type2] varchar(4)) 
; 

INSERT INTO @Table1 ([Start], [End], [Type1], [Type2]) VALUES 
(0, 2, 'L', NULL), 
(2, 3, NULL, 'S'), 
(2, 5, 'L', NULL), 
(3, 5, NULL, 'S'), 
(5, 7, 'L', NULL), 
(5, 8, NULL, 'S'), 
(7, 10, 'L', NULL), 
(11, 12, NULL, 'S'), 

(15, 20, 'L', NULL), 
(15, 20, NULL, 'S'); 

쿼리

WITH 
e1(n) AS 
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
) -- 10 
,e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b) -- 10*10 
,e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2) -- 10*100 
,CTE_Numbers 
AS 
(
    SELECT ROW_NUMBER() OVER (ORDER BY n) AS Number 
    FROM e3 
) 
,CTE_T1 
AS 
(
    SELECT 
     T1.[Start] + CA.Number - 1 AS Value 
     ,T1.Type1 
    FROM 
     @Table1 AS T1 
     CROSS APPLY 
     (
      SELECT TOP(T1.[End] - T1.[Start]) CTE_Numbers.Number 
      FROM CTE_Numbers 
      ORDER BY CTE_Numbers.Number 
     ) AS CA 
    WHERE 
     T1.Type1 IS NOT NULL 
) 
,CTE_T2 
AS 
(
    SELECT 
     T2.[Start] + CA.Number - 1 AS Value 
     ,T2.Type2 
    FROM 
     @Table1 AS T2 
     CROSS APPLY 
     (
      SELECT TOP(T2.[End] - T2.[Start]) CTE_Numbers.Number 
      FROM CTE_Numbers 
      ORDER BY CTE_Numbers.Number 
     ) AS CA 
    WHERE 
     T2.Type2 IS NOT NULL 
) 
,CTE_Values 
AS 
(
    SELECT 
     ISNULL(CTE_T1.Value, CTE_T2.Value) AS Value 
     ,CTE_T1.Type1 
     ,CTE_T2.Type2 
     ,ROW_NUMBER() OVER (ORDER BY ISNULL(CTE_T1.Value, CTE_T2.Value)) AS rn 
    FROM 
     CTE_T1 
     FULL JOIN CTE_T2 ON CTE_T2.Value = CTE_T1.Value 
) 
,CTE_Groups 
AS 
(
    SELECT 
     Value 
     ,Type1 
     ,Type2 
     ,rn 
     ,ROW_NUMBER() OVER 
      (PARTITION BY rn - Value, Type1, Type2 ORDER BY Value) AS rn2 
    FROM CTE_Values 
) 
SELECT 
    MIN(Value) AS [Start] 
    ,MAX(Value) + 1 AS [End] 
    ,Type1 
    ,Type2 
FROM CTE_Groups 
GROUP BY rn-rn2, Type1, Type2 
ORDER BY [Start]; 

결과

+-------+-----+-------+-------+ 
| Start | End | Type1 | Type2 | 
+-------+-----+-------+-------+ 
|  0 | 2 | L  | NULL | 
|  2 | 8 | L  | S  | 
|  8 | 10 | L  | NULL | 
| 11 | 12 | NULL | S  | 
| 15 | 20 | L  | S  | 
+-------+-----+-------+-------+ 
+0

좋은 해결책! 그래도 더 일반적인 만들 수 있는지 궁금해. 예를 들어 시작과 끝 숫자가 소수가 아닌 정수 인 경우 비슷한 스타일의 쿼리를 사용할 수 있습니까? – Harry

+0

@Harry,이 접근법은 간단하고'Start'와'End'가 정수이고 그 사이의 값의 범위가 너무 크지 않다는 사실을 사용합니다. 'Start = 1'과'End = 1000000'을 가지고 있다면 질의는 1M 행을 생성 할 것이고 이것은 느릴 것입니다. 현재 간격으로 처리해야하는 경우 Itzik의 [Packing Intervals] (http://blogs.solidq.com/en/sqlserver/packing-intervals/) 문서를 자세히 살펴보고 그 방법을 조정하십시오. SQL Server 2008에서는 'ROW_NUMBER'만 사용하지만 누적 형 'SUM'을 사용하면 2012 년에 더 효율적으로 만들 수 있습니다. –

+0

"포장 간격"은 최종 해결책이 아닙니다. 소스 원시 데이터를 단순화하고 통합하는 첫 번째 단계입니다. 그런 다음'Type1'과'Type2' 간격을 교차하는 두 번째 단계가 필요합니다. 또는. 다른 방법. 모든 원래 간격을 교차시킨 다음, 간격의 모든 깨진 조각을 병합하기 위해 "포장 간격"의 패스를가집니다. –

0

단계별 방법은 다음과 같습니다

상황은 그들 모두를 분석에 적합하지 않을 수 위의 질의에서
-- Finding all break points 
;WITH breaks AS (
    SELECT Start 
    FROM yourTable 
    UNION 
    SELECT [End] 
    FROM yourTable 
) -- Finding Possible Ends 
, ends AS (
    SELECT Start 
     , (SELECT Min([End]) FROM yourTable WHERE yourTable.Start = breaks.Start) End1 
     , (SELECT Max([End]) FROM yourTable WHERE yourTable.Start < breaks.Start) End2 
    FROM breaks 
) -- Finding periods 
, periods AS (
    SELECT Start, 
     CASE 
      WHEN End1 > End2 And End2 > Start THEN End2 
      WHEN End1 IS NULL THEN End2 
      ELSE End1 
     END [End] 
    FROM Ends 
    WHERE NOT(End1 IS NULL AND Start = End2) 
) -- Generating results 
SELECT p.Start, p.[End], Max(Type1) Type1, Max(Type2) Type2 
FROM periods p, yourTable t 
WHERE p.start >= t.Start AND p.[End] <= t.[End] 
GROUP BY p.Start, p.[End]; 

, 당신이로 향상시킬 수 있습니다 당신이 원한다;).

+0

나는 당신이 언급 한 그러한 "상황들"중 몇 가지를 발견했으며 그들에게 도움이되도록 질의를 개선하기 위해 고심하고 있습니다. 나는보기 위해 구체적인 예를 들어 보려고 노력할 것이다. – Harry

0

먼저 Union을 통해 시작과 끝의 모든 숫자를 가져옵니다.
그런 다음 'L'및 'S'레코드 모두에 해당 번호를 결합하십시오.

테스트를 위해 테이블 ​​변수를 사용합니다.

DECLARE @Table1 TABLE (Start int, [End] int, Type1 varchar(4), Type2 varchar(4)); 

INSERT INTO @Table1 (Start, [End], Type1, Type2) 
VALUES (0, 2, 'L', NULL),(2, 3, NULL, 'S'),(2, 5, 'L', NULL),(3, 5, NULL, 'S'), 
(5, 7, 'L', NULL),(5, 8, NULL, 'S'),(7, 10, 'L', NULL),(11, 12, NULL, 'S'); 

select 
n.Num as Start, 
(case when s.[End] is null or l.[End] <= s.[End] then l.[End] else s.[End] end) as [End], 
l.Type1, 
s.Type2 
from 
(select Start as Num from @Table1 union select [End] from @Table1) n 
left join @Table1 l on (n.Num >= l.Start and n.Num < l.[End] and l.Type1 = 'L') 
left join @Table1 s on (n.Num >= s.Start and n.Num < s.[End] and s.Type2 = 'S') 
where (l.Start is not null or s.Start is not null) 
order by Start, [End]; 

출력 :

Start End Type1 Type2 
0  2 L  NULL 
2  3 L  S 
3  5 L  S 
5  7 L  S 
7  8 L  S 
8  10 L  NULL 
11 12 NULL S 
+0

예제에서는 "L"및 "S"유형 만 표시했지만 실제 데이터 테이블에서는 변수가 있습니다. 이 문장을 좀 더 역동적으로 만들 수 있습니까? – Harry

+0

왼쪽 조인에서 유형에 대한 기준을 'IS NOT NULL'로 변경 한 다음 where 절에서 특정 유형 1 및/또는 유형 2에 대한 기준을 추가 할 수 있습니다. – LukStorms