2010-04-01 89 views
6

Có cách nào tốt hơn để hợp nhất các khoảng ngày trùng lặp không?
Giải pháp tôi đưa ra đơn giản đến mức bây giờ tôi tự hỏi liệu có ai đó có ý tưởng tốt hơn về cách thực hiện điều này không.Hợp nhất các khoảng thời gian chồng chéo ngày

/***** DATA EXAMPLE *****/ 
DECLARE @T TABLE (d1 DATETIME, d2 DATETIME) 
INSERT INTO @T (d1, d2) 
     SELECT '2010-01-01','2010-03-31' UNION SELECT '2010-04-01','2010-05-31' 
    UNION SELECT '2010-06-15','2010-06-25' UNION SELECT '2010-06-26','2010-07-10' 
    UNION SELECT '2010-08-01','2010-08-05' UNION SELECT '2010-08-01','2010-08-09' 
    UNION SELECT '2010-08-02','2010-08-07' UNION SELECT '2010-08-08','2010-08-08' 
    UNION SELECT '2010-08-09','2010-08-12' UNION SELECT '2010-07-04','2010-08-16' 
    UNION SELECT '2010-11-01','2010-12-31' UNION SELECT '2010-03-01','2010-06-13' 

/***** INTERVAL ANALYSIS *****/ 
WHILE (1=1) BEGIN 
    UPDATE t1 SET t1.d2 = t2.d2 
    FROM @T AS t1 INNER JOIN @T AS t2 ON 
      DATEADD(day, 1, t1.d2) BETWEEN t2.d1 AND t2.d2 
    IF @@ROWCOUNT = 0 BREAK 
END 

/***** RESULT *****/ 
SELECT StartDate = MIN(d1) , EndDate = d2 
FROM @T 
GROUP BY d2 
ORDER BY StartDate, EndDate 

/***** OUTPUT *****/ 
/***** 
StartDate EndDate 
2010-01-01 2010-06-13 
2010-06-15 2010-08-16 
2010-11-01 2010-12-31 
*****/ 
+1

Are các khoảng mở mở, đóng kín, mở đóng cửa hoặc đóng mở? Nó quan trọng bởi vì các điều kiện kết thúc khác nhau một chút tùy thuộc. Đối với nhiều mục đích, đóng cửa (bao gồm ngày đầu tiên, không kể ngày thứ hai) là đại diện tốt nhất; mở-mở (cả hai đầu bao gồm) thường là những gì mọi người có trong tâm trí. –

+0

Jonathan, tôi đã suy nghĩ về các trường hợp khi cả ngày (ngày bắt đầu và ngày kết thúc) là một phần của khoảng thời gian. – leoinfo

+0

Có thể thực hiện nó một lần, nhưng đó là việc triển khai con trỏ để nó phụ thuộc vào kích thước của tập dữ liệu. –

Trả lời

0

Trong giải pháp này, tôi đã tạo bảng Lịch tạm thời lưu trữ giá trị hàng ngày trong một phạm vi. Loại bảng này có thể được tạo thành tĩnh. Ngoài ra, tôi chỉ lưu trữ 400 số ngày lẻ bắt đầu từ 2009-12-31. Rõ ràng, nếu ngày của bạn trải rộng trên phạm vi rộng hơn, bạn sẽ cần nhiều giá trị hơn.

Ngoài ra, giải pháp này sẽ chỉ hoạt động với SQL Server 2005+ trong đó tôi đang sử dụng CTE.

With Calendar As 
    (
    Select DateAdd(d, ROW_NUMBER() OVER (ORDER BY s1.object_id), '1900-01-01') As [Date] 
    From sys.columns as s1 
     Cross Join sys.columns as s2 
    ) 
    , StopDates As 
    (
    Select C.[Date] 
    From Calendar As C 
     Left Join @T As T 
      On C.[Date] Between T.d1 And T.d2 
    Where C.[Date] >= (Select Min(T2.d1) From @T As T2) 
     And C.[Date] <= (Select Max(T2.d2) From @T As T2) 
     And T.d1 Is Null 
    ) 
    , StopDatesInUse As 
    (
    Select D1.[Date] 
    From StopDates As D1 
     Left Join StopDates As D2 
      On D1.[Date] = DateAdd(d,1,D2.Date) 
    Where D2.[Date] Is Null 
    ) 
    , DataWithEariestStopDate As 
    (
    Select * 
    , (Select Min(SD2.[Date]) 
     From StopDatesInUse As SD2 
     Where T.d2 < SD2.[Date]) As StopDate 
    From @T As T 
    ) 
Select Min(d1), Max(d2) 
From DataWithEariestStopDate 
Group By StopDate 
Order By Min(d1) 

EDIT Vấn đề với việc sử dụng ngày trong năm 2009 không có gì để làm với các truy vấn chính thức. Vấn đề là bảng Lịch không đủ lớn. Tôi đã bắt đầu bảng Lịch vào 2009-12-31. Tôi đã sửa đổi nó bắt đầu từ 1900-01-01.

+0

Mã của bạn là các khoảng thời gian hợp nhất không được hợp nhất.Sử dụng khoảng thời gian đầu này/**/SELECT '2009-01-01', '2009-01-01' UNION SELECT '2009-01-03', '2009-01-03'/**/mã trả về một khoảng thời gian: 2009-01-01 đến 2009-01-03. Trong trường hợp này 2009-01-02 không nên được bao gồm trong khoảng thời gian kết quả. – leoinfo

+0

Đầu tiên, bạn nên thêm lược đồ và đặc biệt là D1 = D2. Không có dữ liệu mẫu nào của bạn cho thấy điều đó. Thứ hai, nếu bạn ** thêm ** {2010-01-01,2010-01-01}, vào dữ liệu ví dụ hiện tại của bạn, phạm vi đầu tiên sẽ vẫn là 2010-01-01 đến 2010-06-13 vì mục nhập đầu tiên trong ví dụ của bạn bao gồm 2010-01-01 đến 2010-03-31. Thứ ba, nếu bạn thay thế ** thay thế mục nhập đầu tiên trong ví dụ của bạn với {2010-01-01, 2010-01-01}, {2010-03-01, 2010-03-01}, kết quả truy vấn của tôi vẫn đúng. Thực hiện thay đổi đó, hai mục đầu tiên xuất hiện dưới dạng {2010-01-01, 2010-01-01}, {2010-03-01, 2010-06-13}. – Thomas

+0

Một tình huống nữa, nếu bạn thay thế tất cả các mục chỉ với {2010-01-01,2010-01-01}, {2010-03-01,2010-03-01}, bạn sẽ nhận được hai mục nhập đó. – Thomas

0

Hãy thử

;WITH T1 AS 
(
    SELECT d1, d2, ROW_NUMBER() OVER(ORDER BY (SELECT 0)) AS R 
    FROM @T 
), NUMS AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) AS R 
    FROM T1 A 
    CROSS JOIN T1 B 
    CROSS JOIN T1 C 
), ONERANGE AS 
(
    SELECT DISTINCT DATEADD(DAY, ROW_NUMBER() OVER(PARTITION BY T1.R ORDER BY (SELECT 0)) - 1, T1.D1) AS ELEMENT 
    FROM T1 
    CROSS JOIN NUMS 
    WHERE NUMS.R <= DATEDIFF(DAY, d1, d2) + 1 
), SEQUENCE AS 
(
    SELECT ELEMENT, DATEDIFF(DAY, '19000101', ELEMENT) - ROW_NUMBER() OVER(ORDER BY ELEMENT) AS rownum 
    FROM ONERANGE 
) 
SELECT MIN(ELEMENT) AS StartDate, MAX(ELEMENT) as EndDate 
FROM SEQUENCE 
GROUP BY rownum 

Ý tưởng cơ bản này là để đầu cuộn các dữ liệu hiện có, để bạn có được một hàng riêng biệt cho mỗi ngày. Việc này được thực hiện bằng ONERANGE

Sau đó, xác định mối quan hệ giữa cách tăng ngày và cách số hàng thực hiện. Sự khác biệt vẫn không đổi trong phạm vi/đảo hiện có. Ngay sau khi bạn đến một hòn đảo dữ liệu mới, sự khác biệt giữa chúng tăng lên vì ngày tăng thêm hơn 1, trong khi số hàng tăng thêm 1.

13

Tôi đang tìm kiếm giải pháp tương tự và đã xem qua bài đăng này Combine overlapping datetime to return single overlapping range record.

Có một chuỗi khác trên Packing Date Intervals.

Tôi đã thử nghiệm điều này với các phạm vi ngày khác nhau, bao gồm các phạm vi ngày được liệt kê tại đây và nó hoạt động chính xác mọi lúc.


SELECT 
     s1.StartDate, 
     --t1.EndDate 
     MIN(t1.EndDate) AS EndDate 
FROM @T s1 
INNER JOIN @T t1 ON s1.StartDate <= t1.EndDate 
    AND NOT EXISTS(SELECT * FROM @T t2 
       WHERE t1.EndDate >= t2.StartDate AND t1.EndDate < t2.EndDate) 
WHERE NOT EXISTS(SELECT * FROM @T s2 
       WHERE s1.StartDate > s2.StartDate AND s1.StartDate <= s2.EndDate) 
GROUP BY s1.StartDate 
ORDER BY s1.StartDate 

Kết quả là:

StartDate | EndDate 
2010-01-01 | 2010-06-13 
2010-06-15 | 2010-06-25 
2010-06-26 | 2010-08-16 
2010-11-01 | 2010-12-31 
+0

Ngoài ra, tìm thấy một ví dụ khác với giải thích về cách đạt được điều này ở đây: http://www.sqlmag.com/blog/puzzled-by-t-sql-blog-15/tsql/packing-date-intervals-136831 – user1045402

+0

Bạn có thể chỉnh sửa câu trả lời của riêng bạn để thêm thông tin, chỉ cần nhấp vào liên kết "chỉnh sửa" ở cuối câu trả lời của bạn. – ForceMagic

+0

Hoạt động hoàn hảo và ngắn gọn! – ensisNoctis

Các vấn đề liên quan