2014-11-23 56 views
6

我正在使用SQL Server 2008,並且需要創建一個查詢來顯示日期範圍內的行。對日期範圍的行進行分組

我的表如下:

ADM_ID WH_PID  WH_IN_DATETIME WH_OUT_DATETIME 

我的規則是:

  • 如果WH_OUT_DATETIME是上或24小時另一個ADM_ID的WH_IN_DATETIME的內具有相同WH_P_ID

如果可能的話,我希望將另一列添加到結果中,以識別分組的值,如EP_ID

例如

ADM_ID WH_PID EP_ID EP_IN_DATETIME  EP_OUT_DATETIME  WH_IN_DATETIME  WH_OUT_DATETIME 
------ ------ ----- ------------------- ------------------- ------------------- ------------------- 
1  9  1  2014-10-12 00:00:00 2014-10-17 15:00:00 2014-10-12 00:00:00 2014-10-13 15:00:00 
2  9  1  2014-10-12 00:00:00 2014-10-17 15:00:00 2014-10-14 14:00:00 2014-10-15 15:00:00 
3  9  1  2014-10-12 00:00:00 2014-10-17 15:00:00 2014-10-16 14:00:00 2014-10-17 15:00:00 
4  9  2  2014-11-20 00:00:00 2014-11-20 00:00:00 2014-10-16 14:00:00 2014-11-21 00:00:00 
5  5  1  2014-10-17 00:00:00 2014-10-18 00:00:00 2014-10-17 00:00:00 2014-10-18 00:00:00 

的EP_OUT_DATETIME永遠是組中的最後日期:

ADM_ID WH_PID WH_IN_DATETIME   WH_OUT_DATETIME 
------ ------ --------------   --------------- 
1   9   2014-10-12 00:00:00 2014-10-13 15:00:00 
2   9   2014-10-14 14:00:00 2014-10-15 15:00:00 
3   9   2014-10-16 14:00:00 2014-10-17 15:00:00 
4   9   2014-11-20 00:00:00 2014-11-21 00:00:00 
5   5   2014-10-17 00:00:00 2014-10-18 00:00:00 

會和返回行。希望澄清一點。 這樣,我可以按EP_ID進行分組,並找到EP_OUT_DATETIME和任何ADM_ID/PID的啓動時間。


每個人都應該滾入下一個,也就是說,如果另一列具有如下的另一對同一WH_PID的WH_OUT_DATETIME,比該行的WH_OUT_DATETIME成爲所有的WH_PID的是EP_ID內EP_OUT_DATETIME的WH_IN_DATETIME。

我希望這是有道理的。

感謝, MR

+0

完整結構&查詢只是出於好奇,有多少行是該表? – 2014-12-03 14:18:34

+0

@steven您能否提供一些反饋意見,以瞭解我的答案(或其中的任何/全部)中沒有完全符合您的要求的情況,以致賞金未被授予(過期並因此被自動授予50 %)。據我可以從其他答案的問題和評論中知道,至少我的答案,即使不是1或2個答案,也輸出了期望的結果。我在問,因爲200點的賞金表明這個問題對你來說很重要,但對我,迪帕克或斯科特提出的答案沒有反饋意見。 – 2014-12-03 14:27:54

回答

4

由於問題沒有明確規定,該解決方案是一個「單一的」查詢;-),這裏是另一種方法:使用「離奇更新」功能迪利,這是在同一個更新變量當你更新一列。打破這一操作的複雜性,我創建了一個臨時表來存放最難計算的部分:EP_ID。完成後,它將被加入到一個簡單的查詢中,並提供用於計算EP_IN_DATETIMEEP_OUT_DATETIME字段的窗口。

的步驟是:

  1. 所有的ADM_ID值的創建刮表
  2. 種子劃痕表 - 這讓我們做一個更新的所有行已經存在。
  3. 更新劃痕表
  4. 做最後的,簡單的選擇加入劃傷表主表

測試設置

SET ANSI_NULLS ON; 
SET NOCOUNT ON; 

CREATE TABLE #Table 
(
    ADM_ID INT NOT NULL PRIMARY KEY, 
    WH_PID INT NOT NULL, 
    WH_IN_DATETIME DATETIME NOT NULL, 
    WH_OUT_DATETIME DATETIME NOT NULL 
); 

INSERT INTO #Table VALUES (1, 9, '2014-10-12 00:00:00', '2014-10-13 15:00:00'); 
INSERT INTO #Table VALUES (2, 9, '2014-10-14 14:00:00', '2014-10-15 15:00:00'); 
INSERT INTO #Table VALUES (3, 9, '2014-10-16 14:00:00', '2014-10-17 15:00:00'); 
INSERT INTO #Table VALUES (4, 9, '2014-11-20 00:00:00', '2014-11-21 00:00:00'); 
INSERT INTO #Table VALUES (5, 5, '2014-10-17 00:00:00', '2014-10-18 00:00:00'); 

第1步:創建和填充劃痕表

CREATE TABLE #Scratch 
(
    ADM_ID INT NOT NULL PRIMARY KEY, 
    EP_ID INT NOT NULL 
    -- Might need WH_PID and WH_IN_DATETIME fields to guarantee proper UPDATE ordering 
); 

INSERT INTO #Scratch (ADM_ID, EP_ID) 
    SELECT ADM_ID, 0 
    FROM #Table; 

替代劃痕表結構以確保適當的更新順序(自「古怪更新」使用聚集索引的順序,如在此答案的底部說明):

CREATE TABLE #Scratch 
(
    WH_PID INT NOT NULL, 
    WH_IN_DATETIME DATETIME NOT NULL, 
    ADM_ID INT NOT NULL, 
    EP_ID INT NOT NULL 
); 

INSERT INTO #Scratch (WH_PID, WH_IN_DATETIME, ADM_ID, EP_ID) 
    SELECT WH_PID, WH_IN_DATETIME, ADM_ID, 0 
    FROM #Table; 

CREATE UNIQUE CLUSTERED INDEX [CIX_Scratch] 
    ON #Scratch (WH_PID, WH_IN_DATETIME, ADM_ID); 

步驟2:更新暫存表使用一個局部變量來跟蹤先前值

DECLARE @EP_ID INT; -- this is used in the UPDATE 

;WITH cte AS 
(
    SELECT TOP (100) PERCENT 
     t1.*, 
     t2.WH_OUT_DATETIME AS [PriorOut], 
     t2.ADM_ID AS [PriorID], 
     ROW_NUMBER() OVER (PARTITION BY t1.WH_PID ORDER BY t1.WH_IN_DATETIME) 
       AS [RowNum] 
    FROM #Table t1 
    LEFT JOIN #Table t2 
     ON t2.WH_PID = t1.WH_PID 
     AND t2.ADM_ID <> t1.ADM_ID 
     AND t2.WH_OUT_DATETIME >= (t1.WH_IN_DATETIME - 1) 
     AND t2.WH_OUT_DATETIME < t1.WH_IN_DATETIME 
    ORDER BY t1.WH_PID, t1.WH_IN_DATETIME 
) 
UPDATE sc 
SET @EP_ID = sc.EP_ID = CASE 
           WHEN cte.RowNum = 1 THEN 1 
           WHEN cte.[PriorOut] IS NULL THEN (@EP_ID + 1) 
           ELSE @EP_ID 
         END 
FROM #Scratch sc 
INNER JOIN cte 
     ON cte.ADM_ID = sc.ADM_ID 

步驟3:選擇加入擦除表

SELECT tab.ADM_ID, 
     tab.WH_PID, 
     sc.EP_ID, 
     MIN(tab.WH_IN_DATETIME) OVER (PARTITION BY tab.WH_PID, sc.EP_ID) 
      AS [EP_IN_DATETIME], 
     MAX(tab.WH_OUT_DATETIME) OVER (PARTITION BY tab.WH_PID, sc.EP_ID) 
      AS [EP_OUT_DATETIME], 
     tab.WH_IN_DATETIME, 
     tab.WH_OUT_DATETIME 
FROM #Table tab 
INNER JOIN #Scratch sc 
    ON sc.ADM_ID = tab.ADM_ID 
ORDER BY tab.ADM_ID; 

資源

  • MSDN頁UPDATE

    尋找 「@variable =列=表達式」

  • Performance Analysis of doing Running Totals(不完全這裏同樣的事情,但不要太遠離)

    此博客文章提及:

    • PRO:該方法通常是相當快
    • CON:「的更新順序由聚集索引的順序控制的」。根據具體情況,此行爲可能會排除使用此方法。但在這種特殊情況下,如果WH_PID值至少沒有通過聚簇索引的順序自然組合在一起,並且按WH_IN_DATETIME排序,那麼這兩個字段只會添加到臨時表中,並且PK(帶有隱含聚簇索引)劃痕表變爲(WH_PID, WH_IN_DATETIME, ADM_ID)
1

一個Left Outer JoinDateDiff功能應該可以幫助您篩選記錄。最後用Window Function創建GroupID's

create table #test 
(ADM_ID int,WH_PID int,WH_IN_DATETIME DATETIME,WH_OUT_DATETIME DATETIME) 

INSERT #test 
VALUES (1,9,'2014-10-12 00:00:00','2014-10-13 15:00:00'), 
     (2,9,'2014-10-14 14:00:00','2014-10-15 15:00:00'), 
     (3,9,'2014-10-16 14:00:00','2014-10-17 15:00:00'), 
     (1,10,'2014-10-16 14:00:00','2014-10-17 15:00:00'), 
     (2,10,'2014-10-18 14:00:00','2014-10-19 15:00:00') 

SELECT Row_number()OVER(partition by a.WH_PID ORDER BY a.WH_IN_DATETIME) Group_Id, 
     a.WH_PID, 
     a.WH_IN_DATETIME, 
     b.WH_OUT_DATETIME 
FROM #test a 
     LEFT JOIN #test b 
       ON a.WH_PID = b.WH_PID 
       AND a.ADM_ID <> b.ADM_ID 
where Datediff(hh, a.WH_OUT_DATETIME, b.WH_IN_DATETIME)BETWEEN 0 AND 24 

OUTPUT:

Group_Id WH_PID WH_IN_DATETIME   WH_OUT_DATETIME 
-------- ------ ----------------------- ----------------------- 
1   9  2014-10-12 00:00:00.000 2014-10-15 15:00:00.000 
2   9  2014-10-14 14:00:00.000 2014-10-17 15:00:00.000 
1   10  2014-10-16 14:00:00.000 2014-10-19 15:00:00.000 
+0

每個組ID應該有一行,最小IN時間和最大OUT時間 – Steven 2014-11-23 03:15:09

+0

@Steven - 如果WH_PID不同,該怎麼辦? – 2014-11-23 03:19:23

+0

每個組應該是唯一的每個PID。分組是由PID,ADM_ID和日期範圍 – Steven 2014-11-23 03:20:31

3

我會在相關子查詢做到這一點使用exists

select t.*, 
     (case when exists (select 1 
          from table t2 
          where t2.WH_P_ID = t.WH_P_ID and 
           t2.ADM_ID = t.ADM_ID and 
           t.WH_OUT_DATETIME between t2.WH_IN_DATETIME and dateadd(day, 1, t2.WH_OUT_DATETIME) 
         ) 
      then 1 else 0 
     end) as TimeFrameFlag 
from table t; 
+0

您是否有更全面的答案?這隻標記了屬於範圍內的直接標記。需要顯示逐行包含行的東西 – Steven 2014-11-26 14:27:21

+0

@Steven。 。 。我不知道你真的要求什麼。自從我回答以來,問題發生了多次變化。我很確定這解決了原來的問題。 – 2014-11-26 16:34:57

3

嘗試此查詢:

;WITH cte 
    AS (SELECT t1.ADM_ID AS EP_ID,* 
     FROM @yourtable t1 
     WHERE NOT EXISTS (SELECT 1 
          FROM @yourtable t2 
          WHERE t1.WH_PID = t2.WH_PID 
            AND t1.ADM_ID <> t2.ADM_ID 
            AND Abs(Datediff(HH, t1.WH_OUT_DATETIME, t2.WH_IN_DATETIME)) <= 24) 
     UNION ALL 
     SELECT t2.EP_ID,t1.ADM_ID,t1.WH_PID,t1.WH_IN_DATETIME,t1.WH_OUT_DATETIME 
     FROM @yourtable t1 
       JOIN cte t2 
        ON t1.WH_PID = t2.WH_PID 
        AND t1.ADM_ID <> t2.ADM_ID 
        AND Abs((Datediff(HH, t2.WH_IN_DATETIME, t1.WH_OUT_DATETIME))) <= 24), 
    cte_result 
    AS (SELECT t1.*,Dense_rank() OVER (partition BY wh_pid ORDER BY t1.WH_PID, ISNULL(t2.EP_ID, t1.ADM_ID)) AS EP_ID 
     FROM @yourtable t1 
       LEFT OUTER JOIN (SELECT DISTINCT ADM_ID, 
               EP_ID 
           FROM cte) t2 
          ON t1.ADM_ID = t2.ADM_ID) 
SELECT ADM_ID,WH_PID,EP_ID,Min(WH_IN_DATETIME)OVER(partition BY wh_pid, ep_id) AS [EP_IN_DATETIME],Max(WH_OUT_DATETIME)OVER(partition BY wh_pid, ep_id) AS [EP_OUT_DATETIME], 
     WH_IN_DATETIME, 
     WH_OUT_DATETIME 
FROM cte_result 
ORDER BY ADM_ID 

我認爲這些東西:

  • 其按照你的規則,這些行,是group
  • min(WH_IN_DATETIME)將顯示在EP_IN_DATETIME列中屬於該組的所有行。同樣,組的max(WH_OUT_DATETIME)將顯示在EP_IN_DATETIME列中,以查看屬於該組的所有行。
  • EP_ID將分別分配給每個WH_PID的組。
  • 有一件事情沒有被你的問題證明,第四排的EP_OUT_DATETIMEWH_IN_DATETIME分別變爲2014-11-20 00:00:002014-10-16 14:00:00。假設它是一個錯字,它應該是2014-11-21 00:00:00.0002014-11-20 00:00:00.000

釋:

首先CTE cte將返回基於您的規則可能基團。第二個CTE cte_result將分配EP_ID給組。最後,您可以在wh_pid, ep_id的分區中選擇min(WH_IN_DATETIME)Max(WH_OUT_DATETIME)

sqlfiddle

+1

好的答案!有一種情況目前不處理,即如果EP_ID組的最後一行對於同一個ADM_ID具有相互24小時內的「WH_IN_DATETIME」和「WH_OUT_DATETIME」,則會導致所有這些行具有不同的EP_ID分組。例如:在您的SQLFiddle上,將ADM_ID 3更改爲具有'2014-10-17 12:00:00'的WH_OUT_DATETIME時間,您將看到該錯誤。要解決這個問題,只需在'cte'的NOT EXISTS部分的where子句中添加'AND t1.ADM_ID <> t2.ADM_ID'。 – BateTech 2014-11-26 18:44:44

+0

@BateTech非常感謝您徹底查看它。我更新的答案以及SQL與你的建議小提琴。我應該更多地測試它,我的壞。 – 2014-11-27 05:57:06

+0

嗨,你有什麼建議來解決遞歸錯誤嗎?我已經完成了遞歸,並且cte不起作用。我已經嘗試了一些東西,包括使用遊標分別執行每個pid。我發現每個PID的最大計數是9.如果我禁用遞歸,它會運行很長時間(我在16分鐘後停止它)。 – user4283270 2014-12-02 11:02:45

2

這裏的另一個備選......這可能仍然想念你的結果。

我同意@NoDisplayName在ADM_ID 5輸出中出現錯誤,2個OUT日期應該匹配 - 至少對我來說這似乎合乎邏輯。我不明白爲什麼你會想要一個過期日期來顯示日期值,但當然可能有一個很好的理由。 :)

此外,您的問題的措辭使其聽起來像這只是問題的一部分,您可能會採取此輸出進一步。我不確定你的目標是什麼,但是我已經把查詢分解爲2個CTE,你可以在第二CTE中找到你的最終信息(因爲它聽起來像你想把數據分組在一起)。

這裏有SQL Fiddle

-- The Cross Join ensures we always have a pair of first and last time pairs 
-- The left join matches all overlapping combinations, 
-- allowing the where clause to restrict to just the first and last 
-- These first/last pairs are then grouped in the first CTE 
-- Data is restricted in the second CTE 
-- The final select is then quite simple 
With GroupedData AS (
    SELECT 
     (Row_Number() OVER (ORDER BY t1.WH_PID, t1.WH_IN_DATETIME) - 1)/2 Grp, 
     t1.WH_IN_DATETIME, t1.WH_OUT_DATETIME, t1.WH_PID 
    FROM yourtable t1 
    CROSS JOIN (SELECT 0 AS [First] UNION SELECT 1) SetOrder 
    LEFT OUTER JOIN yourtable t2 
     ON t1.WH_PID = t2.WH_PID 
     AND ((DATEADD(d,1,t1.WH_OUT_DATETIME) BETWEEN t2.WH_IN_DATETIME AND t2.WH_OUT_DATETIME AND [First] = 0) 
      OR (DATEADD(d,1,t2.WH_OUT_DATETIME) BETWEEN t1.WH_IN_DATETIME AND t1.WH_OUT_DATETIME AND [First] = 1)) 
    WHERE t2.WH_PID IS NULL 
), RestrictedData AS (
    SELECT WH_PID, MIN(WH_IN_DATETIME) AS WH_IN_DATETIME, MAX(WH_OUT_DATETIME) AS WH_OUT_DATETIME 
    FROM GroupedData 
    GROUP BY Grp, WH_PID 
) 
SELECT yourtable.ADM_ID, yourtable.WH_PID, RestrictedData.WH_IN_DATETIME AS EP_IN_DATETIME, RestrictedData.WH_OUT_DATETIME AS EP_OUT_DATETIME, yourtable.WH_IN_DATETIME, yourtable.WH_OUT_DATETIME 
FROM RestrictedData 
INNER JOIN yourtable 
    ON RestrictedData.WH_PID = yourtable.WH_PID 
    AND yourtable.WH_IN_DATETIME BETWEEN RestrictedData.WH_IN_DATETIME AND RestrictedData.WH_OUT_DATETIME 
ORDER BY yourtable.ADM_ID