【问题标题】:Find groups with matching rows查找具有匹配行的组
【发布时间】:2017-03-09 16:39:31
【问题描述】:

我有一桌人 (CarOwners) 和他们拥有的汽车类型

+-------+-------+
| Name  | Model |
+-------+-------+
| Bob   | Camry |
| Bob   | Civic |
| Bob   | Prius |
| Kevin | Civic |
| Kevin | Focus |
| Mark  | Civic |
| Lisa  | Focus |
| Lisa  | Civic |
+-------+-------+

给定一个名字,我如何找到与完全相同相同汽车的其他人?例如,如果我以 Mark 为目标,则没有其他人只有 Civic,因此查询不会返回任何内容。如果我以 Lisa 为目标,查询将返回

+-------+-------+
| Name  | Model |
+-------+-------+
| Kevin | Civic |
| Kevin | Focus |
+-------+-------+

因为凯文和丽莎的车一模一样。如果我以 Kevin 为目标,查询将返回 Lisa。

我创建了一个包含我的目标人员汽车的 cte,但我不确定如何实现“完全匹配”要求。我所有的尝试都返回带有子集匹配的结果。

with LisaCars as (
    SELECT Model FROM CarOwners WHERE Name = 'Lisa'
)
SELECT Name, Model
FROM CarOwners
WHERE Model in (SELECT * FROM LisaCars) AND Name != 'Lisa'

此查询将返回所有拥有 Civic 或 Focus 的人,这不是我想要的。

+-------+-------+
| Name  | Model |
+-------+-------+
| Bob   | Civic |
| Kevin | Civic |
| Kevin | Focus |
| Mark  | Civic |
+-------+-------+

【问题讨论】:

  • 所以你想要完全匹配,而不是部分匹配?即只有当一个人拥有与所查询的人完全相同的汽车(不多不少)?
  • 是的。我的示例查询已经提供了部分匹配项。

标签: sql sql-server


【解决方案1】:

这使用common table expression(cte) 和count() over() 计算每个name 的行数。

然后matches cte 使用自连接,其中名称不匹配,模型匹配,每个名称的模型计数匹配,其中一个名称是 'Lisa'having 子句确保匹配行数 (count(*)) 与 name 拥有的模型数相匹配。

matches 本身只会返回每个人的 name,因此我们连接回源表 t 以获取每场比赛的完整模型列表。

;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 演示:http://rextester.com/SUKP78304

返回:

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

我们也可以在没有 ctes 的情况下编写它,但它会使它更难理解:

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

【讨论】:

  • @EricGuan 乐于助人!
  • 是否可以将and x.name = 'Lisa' 更改为名称列表,然后接收由完全相同的汽车所有权划分的结果集?现在,我在 Java 中使用循环调用此查询,传递每个名称​​。感觉效率极低。
  • 嗨 Zim,我在这里发布了新问题,并附上了我想要的结果,希望你能继续拯救我的一天! stackoverflow.com/questions/42821465/…。我也会查看你的链接。
【解决方案2】:

试试看

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

【讨论】:

    【解决方案3】:

    一种方法是比较每个名称的有序串联模型值。

    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'
    

    如果你的SQL Server版本支持STRING_AGG,查询可以简化为

    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'
    

    【讨论】:

      【解决方案4】:

      由于您希望匹配准确,我们应该将每个人拥有的汽车数量添加为附加字段。假设您的表名是 '#owners' 以下查询

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

      给我们桌子

      +-------+-------+-----+
      | 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   |
      +-------+-------+-----+
      

      然后我们想将这个表加入到自身匹配的模型和计数中。我们使用 CTE 以便更好地阅读。以下查询

      ; 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
      

      给我们这张桌子

      +-------+-------+-----+-------+-------+-----+
      | 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   |
      +-------+-------+-----+-------+-------+-----+
      

      最后,您可以按所需名称过滤结果

      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
      

      【讨论】:

        【解决方案5】:

        试试这个,我认为它更简单,代码更短,只有一个分区函数。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-08-11
          • 1970-01-01
          • 1970-01-01
          • 2014-02-14
          • 2017-02-02
          • 2015-10-09
          • 1970-01-01
          相关资源
          最近更新 更多