【问题标题】:T-SQL: Islands and Gaps, Start/EndT-SQL:孤岛和差距,开始/结束
【发布时间】:2017-02-20 14:24:38
【问题描述】:

我有一个数据集,我必须在其中合并两个特定字段的范围。在研究过程中,我看到这个问题被称为“岛屿和差距”。不幸的是,大多数 IAG 都涉及在单个列(“StartDate”)或其他内容中合并。而在我的问题中,我有两列构成一个范围(PlzVon-PlzBis)。

我找到了很多例子,但它们往往都是关于给猫剥皮的。其中一些使用 CTE,另一些使用 LAG/LEAD,这是我迄今为止从未听说过的。

我尝试重新利用我找到的一个脚本,主要是因为我能理解它在说什么,但没有骰子。我可以看到问题的所有“部分”(找到上/下端点,匹配两条记录),但我不知道如何用它形成一个连贯的陈述。

在上面的照片中,我想组合突出显示的行,使它们分别是 PlzVon-73000 和 PlzBis-74999。我可以说我需要一个 CTE 或一个相关的子查询,并且具有 b.PlzVon = a.PlzBis + 1 的 ON 条件。但是单个连接是不够的,因为新记录可以与另一个连接,带领我们走上递归和游标的糟糕道路。

任何有关如何合并这些岛屿的帮助将不胜感激。

【问题讨论】:

  • 这两个突出显示的行有什么独特之处,可以用来确定这两行是否要合并为一行? PlzVon-38000 的第一行是什么导致它被排除在与其他两行组合之外?
  • 您使用什么版本的 SQL Server?请添加相应的标签。
  • 看起来不像gaps-and-islands 问题。看看 Itzik Ben-Gan 的 Packing Intervals。如果您在问题和您的预期结果中以文本或INSERT 语句(不是图像)的形式提供一些示例数据,那么很有可能有人会编写有效的查询。
  • @VladimirBaranov 这是 2014 版,我已经按照您的要求添加了标签。
  • @JohnH ID_FI 和 ID_PE 字段用于标识它们指向的“实体”。我应该将其添加到我的问题中。

标签: tsql subquery sql-server-2014 gaps-and-islands


【解决方案1】:

如果我正确理解了这个问题,那么这是一个数据岛问题,但在一个范围而不是一个键上。以下查询演示了如何使用数据岛方法解决此问题。

DECLARE @SourceData TABLE 
(
     ID         INT
    ,PlzVon     INT
    ,PlzBis     INT
)
INSERT INTO @SourceData 
VALUES
(1,38000,38999),
(2,73000,73999),
(3,74000,74999),
(4,75000,75999),
(5,85000,85999);


;WITH CTE_DataIslands  -- First CTE determine the start of each new data island
AS
(
    SELECT           [Main].ID
                    ,[Main].[PlzVon]
                    ,[Main].[PlzBis]
                    ,(
                        CASE
                            WHEN (LAG([Main].[PlzBis], 1) OVER  (ORDER BY [Main].[PlzVon] ASC) + 1 ) <> ([Main].[PlzVon]) THEN 1 -- If prev record's value for ([PlzBis] + 1)  is not equal to current record [PlzVon] value then it is the start of a new data island.
                            ELSE 0
                        END
                     ) AS [IsNewDataIsland]
    FROM            @SourceData [Main]
), CTE_GenerateGroupingID
AS
(
    SELECT  ID
            ,[PlzVon]
            ,[PlzBis]
            ,SUM([IsNewDataIsland]) OVER (ORDER BY [PlzVon] ROWS UNBOUNDED PRECEDING) AS GroupingID -- Create a running total of the IsNewDataIsland column this will create a grouping id we can now group on
    FROM    CTE_DataIslands
)
SELECT      MIN([PlzVon]) AS [PlzVon]       -- Min [PlzVon] will give the lower range
            ,MAX([PlzBis]) AS [PlzBis]      -- Max [PlzBis] will give the upper range (use min or max for any other column that should be included in the return result)
FROM        CTE_GenerateGroupingID
GROUP BY    GroupingID

【讨论】:

    【解决方案2】:

    我不清楚 PK 是什么,所以我在示例表中添加了一个 ID

    Declare @YourTable Table (ID int,PlzVon int,PlzBis int)
    Insert Into @YourTable values
    (1,38000,38999),
    (1,73000,73999),
    (1,74000,74999)
    
    ;with cte0(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N))
         ,cteN(N) As (Select Top (Select max(PlzBis)-min(PlzVon)+1 From @YourTable) 
                             N=(Select min(PlzVon)-1 from @YourTable)+Row_Number() over (Order By (Select NULL)) 
                       From  cte0 N1, cte0 N2, cte0 N3, cte0 N4, cte0 N5, cte0 N6) 
         ,cteBase As (Select A.ID
                            ,A.PlzVon
                            ,A.PlzBis
                            ,PosNr = N.N
                            ,RowNr = N.N - Row_Number() Over (Partition By A.ID Order By N.N) 
                       From  cteN N
                       Join  @YourTable A on N.N Between A.PlzVon and A.PlzBis
                     )
    Select ID
          ,PlzVon = min(PosNr)
          ,PlzBis = max(PosNr)
     From  cteBase
     Group By ID,RowNr
     Order By ID,min(PosNr)
    

    返回

    ID  PlzVon  PlzBis
    1   38000   38999
    1   73000   74999
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-06
      • 2014-04-05
      相关资源
      最近更新 更多