【问题标题】:Merging records with consecutive dates in SQL在 SQL 中合并具有连续日期的记录
【发布时间】:2011-12-03 02:31:53
【问题描述】:

我有以下表架构:

RecordId    EmpID       AbsCode DateFrom    DateTo
---------------------------------------------------------------
666542      1511        AB      09/11/2011  10/11/2011
666986      1511        AB      11/11/2011  11/11/2011
666996      1511        EL      13/11/2011  17/11/2011
755485      1787        SL      01/11/2011  14/11/2011
758545      1787        SL      15/11/2011  26/11/2011
796956      1954        AB      09/11/2011  09/11/2011
799656      1367        AB      09/11/2011  09/11/2011
808845      1527        EL      16/11/2011  16/11/2011
823323      1527        EL      17/11/2011  17/11/2011
823669      1527        EL      18/11/2011  18/11/2011
899555      1123        AB      09/11/2011  09/11/2011
990990      1511        AB      12/11/2011  12/11/2011

如您所见,同一员工的数据是单独输入的。假设他报告了一天的SL(病假),输入了数据,然后他第二天打电话报告另一个病假两天..等等。现在我想要的是在将这些条目呈现给客户时合并这些条目,以便合并具有相同缺勤代码的所有连续缺勤。例如,上表应如下所示:

EmpID       AbsCode DateFrom    DateTo
-------------------------------------------------------------
1511        AB      09/11/2011  12/11/2011
1511        EL      13/11/2011  17/11/2011
1787        SL      01/11/2011  26/11/2011
1954        AB      09/11/2011  09/11/2011
1367        AB      09/11/2011  09/11/2011
1527        EL      16/11/2011  18/11/2011
1123        AB      09/11/2011  09/11/2011

我不是 SQL 人,我可以使用 C# 中的循环来迭代 DataSetDataReader 但我希望在存储过程中使用 T-SQL 来做到这一点。我在 StackOverFlow 中发现了类似的问题,并检查了所有问题,没有一个适用于上述示例表。

编辑: 有时我会遇到这样的情况:

RecordId    EmpID       AbsCode DateFrom    DateTo
---------------------------------------------------------------
666542      1511        AB      09/11/2011  10/11/2011
666986      1511        AB      11/11/2011  25/12/2011

如您所见,该员工从 2011 年 9 月 11 日(d/M/yyyy)到(2011 年 12 月 25 日)缺勤,但客户要求在 12 月 1 日到 12 月 31 日期间缺勤,所以结果应该是:

EmpID       AbsCode DateFrom    DateTo
-------------------------------------------------------------
1511        AB      01/12/2011  12/11/2011

所以基本上,它将根据提供的参数(从,到)显示结果。如果记录在请求的时间段之前开始,它会显示它,但同时它会根据提供的参数显示记录的开始,同样适用于(从,到)参数之后的记录结束。

【问题讨论】:

  • 测试数据错误!所有的日期恰好是连续的。如果员工只有第 1 次和第 30 次缺勤,则应在结果中显示为单独的两行,而不是长时间缺勤。
  • 只是为了展示这个想法..随时添加更多

标签: sql-server tsql


【解决方案1】:

这是一个 CTE,所以它都需要作为一个整体执行,但我会边走边解释。

首先我将设置我们感兴趣的日期范围的参数:

DECLARE @StartDate DateTime; SET @StartDate = '2011-11-01';  
DECLARE @EndDate DateTime; SET @EndDate = '2011-11-30';  

然后我将使用递归 CTE 将它们变成日期列表

WITH 
    ValidDates ( ValidDate ) AS 
        (
            SELECT @StartDate 
                UNION ALL
            SELECT DateAdd(day, 1, ValidDate) 
                FROM ValidDates 
                WHERE ValidDate < @EndDate
        ),

通过将其与原始记录中的范围相结合,我得到了缺勤天数的列表。

结合使用 row_number 和 datediff 我可以对连续日期进行分组。这假设没有重复。

    DaysAbsent AS 
        (
            SELECT 
                  A.RecordID
                , A.EmpID
                , A.AbsCode
                , DateDiff(Day, @StartDate, D.ValidDate) 
                    - row_number() 
                        over (partition by A.EmpID, A.AbsCode  
                            order by D.ValidDate) AS DayGroup
                , D.ValidDate AS AbsentDay
            FROM 
                dbo.Absence A
                    INNER JOIN  
                ValidDates D
                    ON D.ValidDate >= DateFrom 
                       and  D.ValidDate <= DateTo 
        )

现在它是一个简单的选择,带有最小值和最大值,可以将其重新转换为范围。

SELECT 
      EmpID
    , AbsCode
    , MIN(AbsentDay) AS DateFrom
    , MAX(AbsentDay) AS DateTo
FROM
    DaysAbsent
GROUP BY
      EmpID
    , AbsCode
    , DayGroup

输出中不需要 DayGroup,但分组需要,否则不连续的组将折叠为一个。

【讨论】:

  • 它奏效了,我在一名员工身上进行了测试,结果是正确的。我将在生产数据库上对其进行更多测试,我会告诉你.. 谢谢
  • 我在生产数据库上测试过,不是很大的数据库。大约有 500k 行。有时需要 30 秒。我认为如果有一种方法可以在没有递归的情况下执行 ValidDates,它会更快
  • 性能是一个全新的问题。为什么不发布另一个,参考这个并提供源表上可用索引的详细信息。
【解决方案2】:

这将为您提供每位员工休假的天数。我认为他们希望在日期范围内看到这一点。您可以使用以下方法将 varchars 转换为日期:DATEDIFF(DAY, CONVERT(DATETIME,[DateFrom],103), CONVERT(DATETIME,[DateTo],103))

DECLARE @myTable TABLE
(
    RecordId    INT,
    EmpID       INT,
    AbsCode     VARCHAR(2),
    DateFrom    VARCHAR(12),
    DateTo      VARCHAR(12)
)
INSERT INTO @myTable
(
    RecordId,
    EmpID,
    AbsCode,
    DateFrom,
    DateTo
)
SELECT 666542, 1511, 'AB', '09/11/2011', '10/11/2011' UNION ALL 
SELECT 666986, 1511, 'AB', '11/11/2011', '11/11/2011' UNION ALL 
SELECT 666996, 1511, 'EL', '13/11/2011', '17/11/2011' UNION ALL 
SELECT 755485, 1787, 'SL', '01/11/2011', '14/11/2011' UNION ALL 
SELECT 758545, 1787, 'SL', '15/11/2011', '26/11/2011' UNION ALL 
SELECT 796956, 1954, 'AB', '09/11/2011', '09/11/2011' UNION ALL 
SELECT 799656, 1367, 'AB', '09/11/2011', '09/11/2011' UNION ALL 
SELECT 808845, 1527, 'EL', '16/11/2011', '16/11/2011' UNION ALL 
SELECT 823323, 1527, 'EL', '17/11/2011', '17/11/2011' UNION ALL 
SELECT 823669, 1527, 'EL', '18/11/2011', '18/11/2011' UNION ALL 
SELECT 899555, 1123, 'AB', '09/11/2011', '09/11/2011' UNION ALL 
SELECT 990990, 1511, 'AB', '12/11/2011', '12/11/2011'


SELECT [RecordId], [EmpID], [AbsCode], SUM(DAYS) NoDays
FROM
(
    SELECT [RecordId], [EmpID], [AbsCode], DATEDIFF(DAY, CONVERT(DATETIME,[DateFrom],103), CONVERT(DATETIME,[DateTo],103)) Days
    FROM @myTable
    GROUP BY [RecordId], [EmpID], [AbsCode], DATEDIFF(DAY, CONVERT(DATETIME,[DateFrom],103), CONVERT(DATETIME,[DateTo],103))
) subQuery
GROUP BY [RecordId], [EmpID], [AbsCode]

【讨论】:

  • 这是一个简单的解决方案,但不是一个问题!
  • 我在回答中提到了这一点——我的回答更多的是仅供参考。无论如何感谢 -1。
【解决方案3】:
Select EmpId, AbsCode, MIN(DateFrom) as DateFrom, MAX(DateTo) as DateTo From YOURTABLE
Group By EmpId, AbsCode

【讨论】:

  • 谢谢,我觉得我不够清楚,我编辑了问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-20
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
  • 2013-03-24
相关资源
最近更新 更多