【问题标题】:Combining split date ranges in a SQL query在 SQL 查询中组合拆分日期范围
【发布时间】:2010-09-13 12:04:13
【问题描述】:

我正在处理一个需要根据日期范围组合一些数据行的查询。这些行在所有数据值中都重复,但日期范围被拆分。例如表格数据可能看起来像

StudentID   StartDate   EndDate     Field1  Field2
1           9/3/2007    10/20/2007  3       True
1           10/21/2007  6/12/2008   3       True
2           10/10/2007  3/20/2008   4       False
3           9/3/2007    11/3/2007   8       True
3           12/15/2007  6/12/2008   8       True

查询的结果应该组合了拆分日期范围。查询应结合仅间隔一天的日期范围。如果间隔超过一天,则不应合并行。没有拆分日期范围的行应该保持不变。结果看起来像

StudentID   StartDate   EndDate     Field1  Field2
1           9/3/2007    6/12/2008   3       True
2           10/10/2007  3/20/2008   4       False
3           9/3/2007    11/3/2007   8       True
3           12/15/2007  6/12/2008   8       True

该查询的 SELECT 语句是什么?

【问题讨论】:

  • 你能澄清一下每个学生的范围数吗?差距重要吗?
  • 如果一个学生有三个连续的日期范围会怎样?
  • 如果 FieldField2 值在需要合并的行之间发生变化怎么办?那会发生什么?这种改变查询,只是一点点。 :)

标签: sql database ms-access database-design


【解决方案1】:

根据我的经验,我必须在后处理中组合范围(不是在 SQL 中,而是在我的脚本中)。我不确定 SQL 是否可以做到这一点,特别是因为您永远无法确切知道在任何特定情况下需要链接多少个日期范围。如果可以做到这一点,我也很想知道。

编辑:我的回答是假设每个学生有多个日期范围,而不仅仅是开始和结束。如果您只有一个没有间隔的日期范围,那么其他提到的解决方案就是可行的方法。

【讨论】:

    【解决方案2】:
    SELECT StudentID, MIN(startdate) AS startdate, MAX(enddate), field1, field2
    FROM tablex
    GROUP BY StudentID, field1, field2
    

    假设 不是学生的时间范围之间的差距,这将产生结果。

    【讨论】:

      【解决方案3】:
      select StudentID, min(StartDate) StartDate, max(EndDate) EndDate, Field1, Field2 
        from table
       group by StudentID, Field1, Field2
      

      【讨论】:

        【解决方案4】:

        如果 min()/max() 解决方案不够好(例如,如果日期不连续并且您想分别对单独的日期范围进行分组),我想知道使用 Oracle 的 START WITH 和 CONNECT BY 子句是否可行.当然,这并不适用于所有数据库。

        【讨论】:

          【解决方案5】:

          编辑:为 Access 制作另一组 SQL。我测试了所有这些,但是一块一块地测试,因为我不知道如何在 Access 中一次做多个语句。由于我也不知道cmets怎么做,大家可以看看下面SQL版本的cmets。

          select 
          studentid, min(startdate) as Starter, max(enddate) as Ender, field1, field2, 
          max(startDate) - Min(endDate)  as MaxGap 
          into tempIDs
          from student 
          group by studentid, field1, field2 ;  
          
          delete from tempIDs where MaxGap > 1;
          
          UPDATE student INNER JOIN TempIDs ON Student.studentID = TempIDS.StudentID
          SET Student.StartDate = [TempIDs].[Starter],
           Student.EndDate = [TempIDs].[Ender];
          

          我认为是这样,在 SQL Server 中 - 我没有在 Access 中这样做。我还没有针对诸如重叠多个记录等奇特的条件对其进行测试,但这应该可以帮助您入门。它更新所有重复的、小间隔的记录,在数据库中留下额外的记录。 MSDN 有一个关于消除重复的页面:http://support.microsoft.com/kb/139444

          select 
          studentid, min(startdate) as StartDate, max(enddate) as EndDate, field1, field2, 
          datediff(dd, Min(endDate),max(startDate)) as MaxGap 
          into #tempIDs
          from #student 
          group by studentid, field1, field2    
          
          -- Update the relevant records.  Keeps two copies of the massaged record 
          -- - extra will need to be deleted.
          
          update #student 
          set startdate = #TempIDS.startdate, enddate = #tempIDS.EndDate
          from #tempIDS 
          where #student.studentid = #TempIDs.StudentID and MaxGap < 2
          

          【讨论】:

          • 我相信,如果一个学生的表格中有超过 2 行并且其中有两行是相邻的,那么这种情况就会出现问题。
          • 可能在某处发生故障,但这应该是一个很好的起点。
          【解决方案6】:

          以下代码应该可以工作。我做了一些假设如下:日期范围没有重叠,任何字段中都没有 NULL 值,并且给定行的开始日期总是小于结束日期。如果您的数据不符合这些标准,则需要调整此方法,但它应该为您指明正确的方向。

          您可以使用子查询而不是视图,但这可能很麻烦,因此我使用视图使代码更清晰。

          CREATE VIEW dbo.StudentStartDates
          AS
              SELECT
                  S.StudentID,
                  S.StartDate,
                  S.Field1,
                  S.Field2
              FROM
                  dbo.Students S
              LEFT OUTER JOIN dbo.Students PREV ON
                  PREV.StudentID = S.StudentID AND
                  PREV.Field1 = S.Field1 AND
                  PREV.Field2 = S.Field2 AND
                  PREV.EndDate = DATEADD(dy, -1, S.StartDate)
              WHERE PREV.StudentID IS NULL
          GO
          
          CREATE VIEW dbo.StudentEndDates
          AS
              SELECT
                  S.StudentID,
                  S.EndDate,
                  S.Field1,
                  S.Field2
              FROM
                  dbo.Students S
              LEFT OUTER JOIN dbo.Students NEXT ON
                  NEXT.StudentID = S.StudentID AND
                  NEXT.Field1 = S.Field1 AND
                  NEXT.Field2 = S.Field2 AND
                  NEXT.StartDate = DATEADD(dy, 1, S.EndDate)
              WHERE NEXT.StudentID IS NULL
          GO
          
          
          SELECT
              SD.StudentID,
              SD.StartDate,
              ED.EndDate,
              SD.Field1,
              SD.Field2
          FROM
              dbo.StudentStartDates SD
          INNER JOIN dbo.StudentEndDates ED ON
              ED.StudentID = SD.StudentID AND
              ED.Field1 = SD.Field1 AND
              ED.Field2 = SD.Field2 AND
              ED.EndDate > SD.StartDate AND
              NOT EXISTS (SELECT * FROM dbo.StudentEndDates ED2 WHERE ED2.StudentID = SD.StudentID AND ED2.Field1 = SD.Field1 AND ED2.Field2 = SD.Field2 AND ED2.EndDate < ED.EndDate AND ED2.EndDate > SD.StartDate)
          GO
          

          【讨论】:

            【解决方案7】:

            您是否考虑过非 equi 连接?看起来像这样:

            SELECT A.StudentID, A.StartDate, A.EndDate, A.Field1, A.Field2
            FROM tblEnrollment AS A LEFT JOIN tblEnrollment AS B ON (A.StudentID = B.StudentID) 
               AND (A.EndDate=B.StartDate-1)
            WHERE B.StudentID Is Null;
            

            这为您提供的是所有没有对应记录的记录,该记录从第一条记录的结束日期之后的第二天开始。

            [警告:请注意,您只能在 SQL 视图的 Access 查询设计器中编辑非等连接——切换到设计视图可能会导致连接丢失(尽管如果您切换 Access 会告诉您该问题,如果你立即切换回 SQL 视图,你不会丢失它)]

            如果你然后用这个 UNION:

            SELECT A.StudentID, A.StartDate, B.EndDate, A.Field1, A.Field2
            FROM tblEnrollment AS A INNER JOIN tblEnrollment AS B ON (A.StudentID = B.StudentID) 
               AND (A.EndDate= B.StartDate-1)
            

            假设一次不超过两个连续记录,它应该可以满足您的需求。如果你有两个以上的连续记录,我不确定你会怎么做(它可能涉及查看 StartDate-1 与 EndDate 的比较),但这可能会让你朝着正确的方向开始。

            【讨论】:

              【解决方案8】:

              Tom H. 在接受的答案中提供的替代最终查询是

              SELECT
                  SD.StudentID,
                  SD.StartDate,
                  MIN(ED.EndDate),
                  SD.Field1,
                  SD.Field2
              FROM
                  dbo.StudentStartDates SD
              INNER JOIN dbo.StudentEndDates ED ON
                  ED.StudentID = SD.StudentID AND
                  ED.Field1 = SD.Field1 AND
                  ED.Field2 = SD.Field2 AND
                  ED.EndDate > SD.StartDate
              GROUP BY
                  SD.StudentID, SD.Field1, SD.Field2, SD.StartDate
              

              这也适用于所有测试数据。

              【讨论】:

                【解决方案9】:

                这是 SQL(语言)中的一个经典问题,例如在 Joe Celko 的著作“Smarties 的 SQL”(第 23 章,区域、运行、间隙、序列和系列)和他的最新著作“Thinking in Sets”(第 15 章)中进行了介绍。

                虽然在运行时使用怪物查询修复数据很“有趣”,但对我来说,这是可以通过离线和程序更好地修复的情况之一(我个人会使用 Excel 电子表格中的公式来解决)。

                重要的是设置有效的数据库约束,以防止重复出现重叠时段。同样,在 SQL 中编写序列约束是一个经典:请参阅 Snodgrass (http://www.cs.arizona.edu/people/rts/tdbbook.pdf)。给 MS Access 用户的提示:您需要使用 CHECK 约束。

                【讨论】:

                  【解决方案10】:

                  这是一个使用 SQL Server 2005/2008 语法的测试数据示例。

                  DECLARE @Data TABLE(
                      CalendarDate datetime )
                  
                  INSERT INTO @Data( CalendarDate )
                  -- range start
                  SELECT '1 Jan 2010'
                  UNION ALL SELECT '2 Jan 2010'
                  UNION ALL SELECT '3 Jan 2010'
                  -- range start
                  UNION ALL SELECT '5 Jan 2010'
                  -- range start
                  UNION ALL SELECT '7 Jan 2010'
                  UNION ALL SELECT '8 Jan 2010'
                  UNION ALL SELECT '9 Jan 2010'
                  UNION ALL SELECT '10 Jan 2010'
                  
                  SELECT DateGroup, Min( CalendarDate ) AS StartDate, Max( CalendarDate ) AS EndDate
                  FROM(   SELECT NextDay.CalendarDate, 
                              DateDiff( d, RangeStart.CalendarDate, NextDay.CalendarDate ) - ROW_NUMBER() OVER( ORDER BY NextDay.CalendarDate ) AS DateGroup
                          FROM( SELECT Min( CalendarDate ) AS CalendarDate
                                  FROM @data ) AS RangeStart
                              JOIN @data AS NextDay
                                  ON NextDay.CalendarDate >= RangeStart.CalendarDate ) A
                  GROUP BY DateGroup
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-08-14
                    • 2012-01-11
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多