2014-11-23 44 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