递归地对相似项目进行分组

时间:2016-06-27 02:50:09

标签: sql-server recursion

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

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

  • ID
  • 名字
  • DATEOFBIRTH
  • BirthCountry
  • 的GroupID

我需要做的是从表格中的第一个人开始,遍历表格并查找具有相同(LastNameBirthCountry)或具有相同(DateOfBirth的所有人。 {1}}和BirthCountry)。

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

我认为我可以使用多个游标执行此操作,但它变得棘手。

以下是示例数据和输出。

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),因为它们具有相同的(LastName和BirthCountry)。

  • Adam Wayne在Group(100),因为他和John Doe一样(BirthDate和BirthCountry)。

  • Kay Wayne在Group(100)中,因为她与已经在Group(100)中的Adam Wayne具有相同的(LastName和BirthCountry)。

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

  • Jack Stone属于一个团体(100),因为他和Kay Wayne一样(BirthDate和BirthCountry)已经在Group(100)。

数据脚本:

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);

3 个答案:

答案 0 :(得分:1)

我认为groupid是你想要的输出,从100开始。 即使groupid来自另一个表,那也没问题。

首先,抱歉我的“没有游标评论”。此任务需要进行追踪或RBAR操作。事实上经过很长一段时间后我遇到了这么长时间的需求并使用了RBAR操作。

如果明天我可以使用SET BASE METHOD,那么我会来编辑它。

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

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

我使用print进行debuging目的,你可以删除它。

    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

答案 1 :(得分:1)

这是我想出的。我很少编写递归查询,所以这对我来说是一个很好的做法。顺便说一句,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

对于终止它的递归,必须跟踪等价(通过字符串连接),以便在每个级别它只需要考虑新发现的等价(或连接,转换等)。请注意我和#39;我避免使用 group 这个词来避免出现GROUP BY概念。

http://rextester.com/edit/TVRVZ10193

编辑:我对等价使用了几乎任意的编号,但是如果你希望它们出现在基于最低ID的序列中,每个块都很容易做到。当然,row_number()假设min(ID) as grpNum不是ID,而是import UIKit public class MyController: UIViewController { var completionHandler: ((Bool, String) -> Void)? // Bool and String are the return type, you can choose anything you want to return public func onComplete(completion : ((Bool, String) -> Void)?) { self.completionHandler = completion } override public func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override public func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } 是唯一的。

答案 2 :(得分:-1)

也许你可以用这种方式运行它

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