获取每个组的前 5 条记录,并在每个组中将它们连接成一行

Get top 5 records for each group and Concate them in a Row per group

我有一个 table Contacts 基本上如下所示:

Id | Name | ContactId |  Contact   | Amount
---------------------------------------------
1  |  A   |     1     | 12323432   |  555
---------------------------------------------
1  |  A   |     2     | 23432434   |  349
---------------------------------------------
2  |  B   |     3     | 98867665   |  297
--------------------------------------------
2  |  B   |     4     | 88867662   |  142
--------------------------------------------
2  |  B   |     5     |   null     |  698
--------------------------------------------

这里,ContactId在整个table中是唯一的。 Contact 可以是 NULL & 我想排除那些。

现在,我想 select 根据 Amount 每个 Id 的前 5 个联系人。我通过以下查询完成了:

 WITH cte AS (
    SELECT id, Contact, amount, ROW_NUMBER() 
    over (
        PARTITION BY id
        order by amount desc
    ) AS RowNo 
    FROM contacts
    where contact is not null
)
select *from cte where RowNo <= 5

到目前为止一切正常。现在我想为每个组连接这些 (<=5) 记录,并通过连接它们将它们显示在一行中。

预期结果:

 Id | Name | Contact
-------------------------------
  1  |  A   | 12323432;23432434   
-------------------------------
  2  |  B   | 98867665;88867662   

我正在使用以下查询来实现此目的,但它仍然在单独的行中提供所有记录,并且还包括 Null 值:

WITH cte AS (
    SELECT id, Contact, amount,contactid, ROW_NUMBER() 
    over (
        PARTITION BY id
        order by amount desc
    ) AS RowNo 
    FROM contacts
    where contact is not null
)
select *from id, name, 
STUFF ((
          SELECT distinct  '; ' + isnull(contact,'')   FROM cte 
          WHERE co.id= cte.id and co.contactid= cte.contactid
          and RowNo <= 5
 FOR XML PATH('')),1, 1, '')as contact
 from contacts co inner join cte where cte.id = co.id and co.contactid= cte.contactid

以上查询仍然为我提供差异行中的所有前 5 个联系人,也包括 null。

一起使用CTESTUFF是个好主意吗?请提出是否有比这更好的方法。

如果您是 运行 SQL Server 2017 或更高版本,您可以使用 string_agg():与大多数其他聚合函数一样,它会在设计上忽略 null 值。

select id, name, string_agg(contact, ',') within group (order by rn) all_contacts
from (
    select id, name, contact
        row_number() over (partition by id order by amount desc) as rn 
    from contacts
    where contact is not null
) t
where rn <= 5
group by id, name

请注意,您在这里并不严格需要 CTE;您可以 return 从子查询中获取您需要的列,并在外部查询中直接使用它们。

在早期版本中,一种使用 stuff()for xml path 的方法是:

with cte as (
    select id, name, contact, 
        row_number() over (partition by id order by amount desc) as rn 
    from contacts
    where contact is not null
)
select id, name, 
    stuff(
        (
            select ', ' + c1.concat
            from cte c1
            where c1.id = c.id and c1.rn <= 5
            order by c1.rn
            for xml path (''), type
        ).value('.', 'varchar(max)'), 1, 2, ''
    ) all_contacts
from cte
group by id, name

我在最后一个查询中遇到了问题:

我的期末考试 Select 不需要原版 Contact table,因为我在 CTE 中已经拥有我需要的一切。此外,在 STUFF() 中,我正在使用 contactid 加入,这实际上是我在这里尝试连接的。由于我使用该条件进行连接,因此我得到了不同行中的记录。我已经删除了这 2 个条件并且它起作用了。

WITH cte AS (
    SELECT id, Contact, amount,contactid, ROW_NUMBER() 
    over (
        PARTITION BY id
        order by amount desc
    ) AS RowNo 
    FROM contacts
    where contact is not null
)
select *from id, name, 
STUFF ((
          SELECT distinct  '; ' + isnull(contact,'')   FROM cte 
          WHERE co.id= cte.id              
          and RowNo <= 5
 FOR XML PATH('')),1, 1, '')as contact
 from  cte where rowno <= 5

您可以使用条件聚合: 身份证,姓名,联系方式,

select id, name,
       concat(max(case when seqnum = 1 then contact + ';' end),
              max(case when seqnum = 2 then contact + ';' end),
              max(case when seqnum = 3 then contact + ';' end),
              max(case when seqnum = 4 then contact + ';' end),
              max(case when seqnum = 5 then contact + ';' end)
             ) as contacts              
from (select c.*
             row_number() over (partition by id order by amount desc) as seqnum
      from contacts c
      where contact is not null
     ) c
group by id, name;

我同意@GMB。 STRING_AGG() 是你需要的...

WITH
contacts(Id,nm,ContactId,Contact,Amount) AS (
          SELECT 1,'A',1,12323432,555
UNION ALL SELECT 1,'A',2,23432434,349
UNION ALL SELECT 2,'B',3,98867665,297
UNION ALL SELECT 2,'B',4,88867662,142
UNION ALL SELECT 2,'B',5,NULL    ,698
)
,
with_filter_val AS (
  SELECT
    *
  , ROW_NUMBER() OVER(PARTITION BY id ORDER BY amount DESC) AS rn
  FROM contacts
)
SELECT
  id
, nm
, STRING_AGG(CAST(contact AS CHAR(8)),',') AS contact_list
FROM with_filter_val
WHERE rn <=5
GROUP BY
  id
, nm
-- out  id | nm |   contact_list    
-- out ----+----+-------------------
-- out   1 | A  | 12323432,23432434
-- out   2 | B  | 98867665,88867662