【问题标题】:Grouping similar items recursively递归地对相似的项目进行分组
【发布时间】:2016-10-28 22:51:00
【问题描述】:

我一直在阅读以下 Microsoft article 关于使用 CTE 进行递归查询的内容,但我似乎无法理解如何将其用于组常见项目。

我有一个包含以下列的表格:

  • 身份证
  • 名字
  • 姓氏
  • 出生日期
  • 出生国家
  • 组ID

我需要做的是从表格中的第一个人开始,遍历表格并找到所有相同(LastNameBirthCountry)或相同(DateOfBirth 和 @)的人987654325@)。

现在棘手的部分是我必须为他们分配相同的GroupID,然后对于该GroupID 中的每个人,我需要查看其他人是否有相同的信息,然后将它们放入相同的@987654328 @。

我想我可以用多个游标来做到这一点,但它变得越来越棘手。

这是示例数据和输出。

ID          FirstName  LastName   DateOfBirth BirthCountry GroupID
----------- ---------- ---------- ----------- ------------ -----------
1           Jonh       Doe        1983-01-01  Grand        100
2           Jack       Stone      1976-06-08  Grand        100
3           Jane       Doe        1982-02-08  Grand        100
4           Adam       Wayne      1983-01-01  Grand        100
5           Kay        Wayne      1976-06-08  Grand        100
6           Matt       Knox       1983-01-01  Hay          101
  • John Doe 和 Jane Doe 属于同一个组 (100),因为它们具有相同的(姓氏和出生国)。

  • Adam Wayne 属于第 (100) 组,因为他与 John Doe 具有相同的(出生日期和出生国家/地区)。

  • Kay Wayne 在组 (100) 中,因为她与已经在组 (100) 中的 Adam Wayne 具有相同的(姓氏和出生国)。

  • Matt Knox 加入了一个新组 (101),因为他与之前组中的任何人都不匹配。

  • Jack Stone 在组 (100) 中,因为他与已经在组 (100) 中的 Kay Wayne 具有相同的(出生日期和出生国家/地区)。

数据脚本:

CREATE TABLE #Tbl(
    ID              INT,
    FirstName       VARCHAR(50),
    LastName        VARCHAR(50),
    DateOfBirth     DATE,
    BirthCountry    VARCHAR(50),
    GroupID         INT NULL
);

INSERT INTO #Tbl VALUES
(1, 'Jonh', 'Doe',      '1983-01-01',   'Grand',    NULL),
(2, 'Jack', 'Stone',    '1976-06-08',   'Grand',    NULL),
(3, 'Jane', 'Doe',      '1982-02-08',   'Grand',    NULL),
(4, 'Adam', 'Wayne',    '1983-01-01',   'Grand',    NULL),
(5, 'Kay',  'Wayne',    '1976-06-08',   'Grand',    NULL),
(6, 'Matt', 'Knox',     '1983-01-01',   'Hay',      NULL);

【问题讨论】:

  • 为什么 HV 填充 GROUPID ?假设 groupid 是您需要的输出,那么应该从哪个 groupid 开始?100?为什么 groupid 应该从 100 开始?
  • @goroth 你提到了CURSOR。我相信它应该能够解决您的问题。你已经完成了一些事情吗?
  • 至少不要尝试Cursor。
  • @ydoow 我确实让它在某种程度上可以使用双光标和临时表,但它非常缓慢和混乱。

标签: sql-server recursion


【解决方案1】:

或许你可以这样运行

SELECT *
FROM table_name
GROUP BY
    FirstName,
    LastName,
    GroupID
HAVING COUNT(GroupID) >= 2
ORDER BY GroupID

【讨论】:

    【解决方案2】:

    我假设 groupid 是您想要的输出,从 100 开始。 即使 groupid 来自另一个表,也没有问题。

    首先,对不起我的“没有光标 cmets”。这个任务需要光标或 RBAR 操作。事实上,在很长一段时间后,我遇到了这个需要很长时间的要求,我使用了 RBAR 操作。

    如果明天我能够使用 SET BASE METHOD 进行操作,那么我会来编辑它。

    最重要的是使用 RBAR 操作使脚本更容易理解,我认为它也适用于其他示例数据。 还提供有关性能以及它如何与其他示例数据一起使用的反馈。

    Alsi 在我的脚本中你注意到 id 不是串行的,没关系,我这样做是为了测试。

    我使用 print 进行调试,你可以删除它。

        SET NOCOUNT ON
    DECLARE @Tbl TABLE(
        ID              INT,
        FirstName       VARCHAR(50),
        LastName        VARCHAR(50),
        DateOfBirth     DATE,
        BirthCountry    VARCHAR(50),
        GroupID         INT NULL
    );
    
    INSERT INTO @Tbl VALUES
    (1, 'Jonh', 'Doe',      '1983-01-01',   'Grand',    NULL) ,
    (2, 'Jack', 'Stone',    '1976-06-08',   'Grand',    NULL),
    (3, 'Jane', 'Doe',      '1982-02-08',   'Grand',    NULL),
    (4, 'Adam', 'Wayne',    '1983-01-01',   'Grand',    NULL),
    (5, 'Kay',  'Wayne',    '1976-06-08',   'Grand',    NULL),
    (6, 'Matt', 'Knox',     '1983-01-01',   'Hay',      NULL),
    (7, 'Jerry', 'Stone',   '1976-06-08',   'Hay',      NULL)
    
    
    DECLARE @StartGroupid INT = 100
    DECLARE @id INT
    DECLARE @Groupid INT
    DECLARE @Maxid INT
    DECLARE @i INT = 1
    DECLARE @MinGroupID int=@StartGroupid
    DECLARE @MaxGroupID int=@StartGroupid
    DECLARE @LastGroupID int
    SELECT @maxid = max(id)
    FROM @tbl
    
    WHILE (@i <= @maxid)
    BEGIN
        SELECT @id = id
            ,@Groupid = Groupid
        FROM @Tbl a
        WHERE id = @i
    
        if(@Groupid is not null and @Groupid<@MinGroupID)
        set @MinGroupID=@Groupid
        if(@Groupid is not null and @Groupid>@MaxGroupID)
        set @MaxGroupID=@Groupid
        if(@Groupid is not null)
        set @LastGroupID=@Groupid
    
        UPDATE A
        SET groupid =case 
                when @id=1 and  b.groupid is null then @StartGroupid 
                when @id>1 and  b.groupid is null then @MaxGroupID+1--(Select max(groupid)+1 from @tbl where id<@id)
                when @id>1 and  b.groupid is not null then @MinGroupID --(Select min(groupid) from @tbl where id<@id)
        end
        FROM @Tbl A
        INNER JOIN @tbl B ON b.id = @ID
        WHERE (
                (
                    a.BirthCountry = b.BirthCountry
                    and a.DateOfBirth = b.dateofbirth
                    )
                or (a.LastName = b.LastName and a.BirthCountry = b.BirthCountry)
                     or (a.LastName = b.LastName and a.dateofbirth = b.dateofbirth)
                )
    
    --if(@id=7) --@id=2,@id=3 and so on (for debug
    --break
    
        SET @i = @i + 1
        SET @ID = @I
    END
    
    SELECT * 
    FROM @Tbl
    

    替代方法,但它仍然返回 56,000 行而没有 rownum=1。看看它是否适用于其他示例数据,或者看看你是否可以进一步优化它。

    ;with CTE as
    (
        select a.ID,a.FirstName,a.LastName,a.DateOfBirth,a.BirthCountry
        ,@StartGroupid GroupID 
        ,1 rn
        FROM @Tbl A where a.id=1
    
    
    UNION ALL
    
    Select a.ID,a.FirstName,a.LastName,a.DateOfBirth,a.BirthCountry
    
    
     ,case when ((a.BirthCountry = b.BirthCountry and a.DateOfBirth = b.dateofbirth)
                or (a.LastName = b.LastName and a.BirthCountry = b.BirthCountry)
                or (a.LastName = b.LastName and a.dateofbirth = b.dateofbirth)
                ) then b.groupid  else b.groupid+1 end
        , b.rn+1
        FROM @tbl A
       inner join CTE B on a.id>1 
    
       where b.rn<@Maxid
    
    )
    ,CTE1 as
    (select * ,row_number()over(partition by id order by groupid )rownum 
    from CTE )
    
    select * from cte1
    where rownum=1
    

    【讨论】:

    • 它没有将 Jack Stone 放在 100 组中。看起来它每次找到匹配时都不会递归
    • 很晚才回复。为什么 Jack Stone 应该在 100 组?
    • Jack Stone 与 Kay Wayne 具有相同的(BirthDate 和 BirthCountry),并且 Kay Wayne 在第 100 组中,因为她与 Adam Wayne 具有相同的(LastName 和 BirthCountry),而 Adam Wayne 在第 100 组中,因为他与 John Doe 具有相同的(BirthDate 和 BirthCountry)。这是一个非常混乱的链条。
    • @goroth,是的,根据您的逻辑,我的回答是错误的。假设表中只有前 3-4 条记录,那么那组 id=2 ?101 会是什么?所以这个组 id 不是恒定的。每次都会计算。你确定,关于逻辑吗?
    • 是的,我确信逻辑。这是一个相似的人链。我给出的示例仅用于演示目的。在现实世界的数据中,我试图找到类似的数据模式。
    【解决方案3】:

    这就是我想出的。我很少写递归查询,所以这对我来说是一个很好的做法。顺便说一下,Kay 和 Adam 在您的样本数据中没有共享出生国家。

    with data as (
        select
            LastName, DateOfBirth, BirthCountry,
            row_number() over (order by LastName, DateOfBirth, BirthCountry) as grpNum
        from T group by LastName, DateOfBirth, BirthCountry
    ), r as (
        select
            d.LastName, d.DateOfBirth, d.BirthCountry, d.grpNum,
            cast('|'  + cast(d.grpNum as varchar(8)) + '|' as varchar(1024)) as equ
        from data as d
        union all
        select
            d.LastName, d.DateOfBirth, d.BirthCountry, r.grpNum,
            cast(r.equ + cast(d.grpNum as varchar(8)) + '|' as varchar(1024))
        from r inner join data as d
                on      d.grpNum > r.grpNum
                   and charindex('|' + cast(d.grpNum as varchar(8)) + '|', r.equ) = 0
                   and (d.LastName = r.LastName or d.DateOfBirth = r.DateOfBirth)
                   and  d.BirthCountry = r.BirthCountry
    ), g as (
        select LastName, DateOfBirth, BirthCountry, min(grpNum) as grpNum
        from r group by LastName, DateOfBirth, BirthCountry
    )
    select t.*, dense_rank() over (order by g.grpNum) + 100 as GroupID
    from T as t 
        inner join g
            on      g.LastName = t.LastName
                and g.DateOfBirth = t.DateOfBirth
                and g.BirthCountry = t.BirthCountry
    

    为了终止递归,有必要跟踪等价(通过字符串连接),以便在每个级别只需要考虑新发现的等价(或连接、传递性等)。请注意,我已经避免使用group 一词以避免渗入GROUP BY 概念。

    http://rextester.com/edit/TVRVZ10193

    编辑:我对等价项使用了几乎任意的编号,但如果您希望它们以基于每个块的最低 ID 的序列出现,这很容易做到。而不是使用row_number()min(ID) as grpNum 当然,假设IDs 是唯一的。

    【讨论】:

    • 这永远不会在我拥有的数据集上完成运行。大约 500,000 条记录。
    • @goroth 我相信我找到了正确的方法。希望这次对你有用。
    • 虽然表现要好得多,但杰克·斯通并没有进入 100 组。看起来它每次找到匹配项时都不会递归。
    • 其实我错了。如果我将 + 100 更改为 + 99 作为 GroupID,那么 Jack Stone 在正确的 100 组中。我仍在测试更大的数据集。我会告诉你的。
    • @goroth 很奇怪,因为 100 真的不应该改变任何逻辑。
    猜你喜欢
    • 1970-01-01
    • 2014-07-23
    • 1970-01-01
    • 2018-12-23
    • 1970-01-01
    • 2015-02-23
    • 2016-01-13
    • 1970-01-01
    • 2020-06-06
    相关资源
    最近更新 更多