Find groups with matching rows

前端 未结 5 571
庸人自扰
庸人自扰 2020-12-22 01:11

I have a table of people (CarOwners) and the types of cars they own

+-------+-------+
| Name  | Model |
+-------+-------+
| Bob   | Camry |
| Bo         


        
相关标签:
5条回答
  • 2020-12-22 01:48

    Try it's

    if object_id('tempdb.dbo.#temp') is not null
    drop table #temp
    
    create table #temp (name varchar(100),model varchar(100))
    
    insert into #temp values('Bob','Camry')
    insert into #temp values('Bob','Civic')
    insert into #temp values('Bob','Prius')
    insert into #temp values('Kevin','Focus')
    insert into #temp values('Kevin','Civic')
    insert into #temp values('Mark','Civic')
    insert into #temp values('Lisa','Focus')
    insert into #temp values('Lisa','Civic')
    
    select * from (
    select row_number() over(partition by name order by (select null)) as n,
    row_number() over(partition by model order by (select null)) as m,*
    from #temp) as a
    where  n = m
    order by name
    
    0 讨论(0)
  • 2020-12-22 02:00

    One way to do this is comparing the ordered concatenated model value for each name.

    with cte as (
    select name,model,
         STUFF((
              SELECT ',' + t2.model
              FROM t t2
              WHERE t1.name=t2.name
              ORDER BY model
              FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 1, '') concat_value
    from t t1 
    ) 
    select distinct x2.name,x2.model
    from cte x1
    join cte x2 on x1.concat_value=x2.concat_value and x1.name<>x2.name
    where x1.name='Kevin'
    

    If your version of SQL Server supports STRING_AGG, the query can be simplified as

    with cte as (
        select name,model,
             STRING_AGG(model,',') WITHIN GROUP(ORDER BY model) as concat_value
        from t t1 
        ) 
    select distinct x2.name,x2.model
    from cte x1
    join cte x2 on x1.concat_value=x2.concat_value and x1.name<>x2.name
    where x1.name='Kevin'
    
    0 讨论(0)
  • 2020-12-22 02:02

    This counts the number of rows for each name using a common table expression(cte) with count() over().

    Then the matches cte uses a self-join where the names do not match, the models match, the count of models for each name match, and one of those names is 'Lisa'. The having clause ensures that count of matched rows (count(*)) matches the number of models that name has.

    matches itself would only return the name of each person, so we join back to the source table t to get the full list of models for each match.

    ;with cte as (
      select *
        , cnt = count(*) over (partition by name)
      from t
    )
    , matches as (
      select x2.name
      from cte as x 
        inner join cte as x2
           on x.name <> x2.name
          and x.model = x2.model
          and x.cnt   = x2.cnt 
          and x.name  = 'Lisa'
      group by x2.name, x.cnt
      having count(*) = x.cnt
    )
    select t.* 
    from t
      inner join matches m
        on t.name = m.name
    

    rextester demo: http://rextester.com/SUKP78304

    returns:

    +-------+-------+
    | name  | model |
    +-------+-------+
    | Kevin | Civic |
    | Kevin | Focus |
    +-------+-------+
    

    We could also write it without the ctes, but it makes it a little harder to follow:

    select t.*
    from t 
      inner join (
        select x2.Name
        from (
          select *, cnt = count(*) over (partition by name) 
          from t 
          where name='Lisa'
          ) as x
          inner join (
          select *, cnt = count(*) over (partition by name) 
          from t
          ) as x2
            on x.name <> x2.name
           and x.model = x2.model
           and x.cnt   = x2.cnt 
        group by x2.name, x.cnt
        having count(*) = x.cnt
      ) as m 
        on t.name = m.name
    
    0 讨论(0)
  • 2020-12-22 02:11

    Since you want your match to be exact, we should add the number of cars each person owns as an additional field. Assuming your table name is '#owners' The following query

    select  *
            , (select COUNT(*)
                from #owners o2
                where o2.name = o1.name) as num
        from #owners o1
    

    gives us the table

    +-------+-------+-----+
    | Name  | Model | num |
    +-------+-------+-----+
    | Bob   | Camry | 3   |
    | Bob   | Civic | 3   |
    | Bob   | Prius | 3   | 
    | Kevin | Civic | 2   |
    | Kevin | Focus | 2   |
    | Mark  | Civic | 1   |
    | Lisa  | Focus | 2   |
    | Lisa  | Civic | 2   |
    +-------+-------+-----+
    

    Then we want to join this table to itself matching model and count. We use a CTE so that it reads better. The following query

    ; with
        OwnedCount as (
            select  *
                    , (select COUNT(*)
                        from #owners o2
                        where o2.name = o1.name) as num
                from #owners o1
        )
    select *
        from OwnedCount o1
        inner join OwnedCount o2
            on o1.model = o2.model 
            and o1.num = o2.num
    

    gives us this table

    +-------+-------+-----+-------+-------+-----+
    | Name  | Model | num | Name  | Model | num |
    +-------+-------+-----+-------+-------+-----+
    | Bob   | Camry | 3   | Bob   | Camry | 3   |
    | Bob   | Civic | 3   | Bob   | Civic | 3   |
    | Bob   | Prius | 3   | Bob   | Prius | 3   |
    | Kevin | Civic | 2   | Kevin | Civic | 2   |
    | Kevin | Civic | 2   | Lisa  | Civic | 2   |
    | Kevin | Focus | 2   | Kevin | Focus | 2   |
    | Kevin | Focus | 2   | Lisa  | Focus | 2   |
    | Mark  | Civic | 1   | Mark  | Civic | 1   |
    | Lisa  | Civic | 2   | Kevin | Civic | 2   |
    | Lisa  | Civic | 2   | Lisa  | Civic | 2   |
    | Lisa  | Focus | 2   | Kevin | Focus | 2   |
    | Lisa  | Focus | 2   | Lisa  | Focus | 2   |
    +-------+-------+-----+-------+-------+-----+
    

    Lastly, you filter the results by the name you want

    declare @given_name varchar(32) = 'Lisa'
    ; with
        OwnedCount as (
            select  *
                    , (select COUNT(*)
                        from #owners o2
                        where o2.name = o1.name) as num
                from #owners o1
        )
    select o2.name, o2.model
        from OwnedCount o1
        inner join OwnedCount o2
            on o1.model = o2.model 
            and o1.num = o2.num
        where o1.name = @given_name
            and o2.name <> @given_name
    
    0 讨论(0)
  • 2020-12-22 02:12

    try this,i think it is much easier and short code with just one partition function.

        declare @t table(Name varchar(50),Model varchar(50))
        insert into @t values
        ('Bob','Camry')
        ,('Bob','Civic')
        ,('Bob','Prius')
        ,('Kevin','Civic')
        ,('Kevin','Focus')
        ,('Mark','Civic')
        ,('Lisa','Focus')
        ,('Lisa','Civic')
    
        declare @input varchar(50)='Lisa'
    
        ;with 
    CTE1 AS
    (
    select name,model,ROW_NUMBER()over( order by name) rn
     from @t
    where name=@input
    )
    ,cte2 as
    (
    select t.name,t.Model
    ,ROW_NUMBER()over(partition by t.name order by t.name) rn3
    from @t t 
    inner JOIN
    cte1    c on t.Model=c.model 
    where   t.Name !=@input
    )
    select * from cte2 c
    where exists(select rn3 from cte2 c1 
    where c1.name=c.name and c1.rn3=(select max(rn) from cte1)
    )
    
    0 讨论(0)
提交回复
热议问题