在 SQL Server 中使用 STRING_AGG 获取唯一值

Posted

技术标签:

【中文标题】在 SQL Server 中使用 STRING_AGG 获取唯一值【英文标题】:Get unique values using STRING_AGG in SQL Server 【发布时间】:2018-11-08 09:17:14 【问题描述】:

以下查询返回如下所示的结果:

SELECT 
    ProjectID, newID.value
FROM 
    [dbo].[Data] WITH(NOLOCK)  
CROSS APPLY 
    STRING_SPLIT([bID],';') AS newID  
WHERE 
    newID.value IN ('O95833', 'Q96NY7-2') 

结果:

ProjectID   value
---------------------
2           Q96NY7-2
2           O95833
2           O95833
2           Q96NY7-2
2           O95833
2           Q96NY7-2
4           Q96NY7-2
4           Q96NY7-2

使用新添加的STRING_AGG 函数(在 SQL Server 2017 中),如以下查询所示,我能够得到下面的结果集。

SELECT 
    ProjectID,
    STRING_AGG( newID.value, ',') WITHIN GROUP (ORDER BY newID.value) AS 
NewField
FROM
    [dbo].[Data] WITH(NOLOCK)  
CROSS APPLY 
    STRING_SPLIT([bID],';') AS newID  
WHERE 
    newID.value IN ('O95833', 'Q96NY7-2')  
GROUP BY 
    ProjectID
ORDER BY 
    ProjectID

结果:

ProjectID   NewField
-------------------------------------------------------------
2           O95833,O95833,O95833,Q96NY7-2,Q96NY7-2,Q96NY7-2
4           Q96NY7-2,Q96NY7-2

我希望我的最终输出只有如下独特的元素:

ProjectID   NewField
-------------------------------
2           O95833, Q96NY7-2
4           Q96NY7-2

关于如何获得此结果的任何建议?如果需要,请随时从头开始完善/重新设计我的查询。

【问题讨论】:

所以您将数据存储为分隔值,现在您想要拆分它们,找到不同的值,最后将它们全部塞回分隔字符串?呸!!!分隔数据违反 1NF。这就是为什么你在这里如此挣扎的原因。首次拆分后,您必须使用带有 DISTINCT 的 STUFF 和 FOR XML 来执行此操作。 SQL 小提琴:sqlfiddle.com/#!18/0b959/1 关于如何在我的数据集中使用带有 DISTINCT 的 STUFF 和 FOR XML 的任何简单示例?我无法避免 STRING_SPLIT,因为不幸的是,原始数据存储为您意识到的分隔值。 小心那个 NOLOCK 提示。 blogs.sentryone.com/aaronbertrand/bad-habits-nolock-everywhere 【参考方案1】:

Oracle(自版本 19c)suportslistagg (DISTINCT ...,但 Microsoft SQL Server 不太可能。

【讨论】:

OP 没有询问 Oracle【参考方案2】:

这是我编写的用于回答 OP 标题的函数: 欢迎改进!

CREATE OR ALTER FUNCTION [dbo].[fn_DistinctWords]
(
  @String NVARCHAR(MAX)  
)
RETURNS NVARCHAR(MAX)
WITH SCHEMABINDING
AS
BEGIN
  DECLARE @Result NVARCHAR(MAX);
  WITH MY_CTE AS ( SELECT Distinct(value) FROM STRING_SPLIT(@String, ' ')  )
  SELECT @Result = STRING_AGG(value, ' ') FROM MY_CTE
  RETURN @Result
END
GO

像这样使用:

SELECT dbo.fn_DistinctWords('One Two      Three Two One');

【讨论】:

【参考方案3】:

STRING_AGG 获取唯一字符串的另一种可能性是在获取逗号分隔的字符串后执行这三个步骤:

    拆分字符串 (STRING_SPLIT) 从拆分中选择DISTINCT 再次将STRING_AGG 应用于单个键上的组选择

例子:

(select STRING_AGG(CAST(value as VARCHAR(MAX)), ',') 
        from (SELECT distinct 1 single_key, value 
            FROM STRING_SPLIT(STRING_AGG(CAST(customer_division as VARCHAR(MAX)), ','), ',')) 
                q group by single_key) as customer_division

【讨论】:

【参考方案4】:

您可以创建一个独特的表格视图,其中包含聚合值,这更加简单:

Create Table Test (field1 varchar(1), field2 varchar(1));

go

Create View DistinctTest as (Select distinct field1, field2 from test group by field1,field2);

go

insert into Test Select 'A', '1';
insert into Test Select 'A', '2';
insert into Test Select 'A', '2';
insert into Test Select 'A', '2';
insert into Test Select 'D', '1';
insert into Test Select 'D', '1';

select string_agg(field1, ',')  from Test where field2 = '1';  /* duplicates: A,D,D */;

select string_agg(field1, ',')  from DistinctTest where field2 = '1';  /* no duplicates: A,D  */;

【讨论】:

【参考方案5】:

正如@SeanLange 在 cmets 中指出的那样,这是一种提取数据的糟糕方法,但如果您不得不这样做,只需按如下方式进行 2 个单独的查询:

SELECT 
    ProjectID
    ,STRING_AGG( val, ',') WITHIN GROUP (ORDER BY val) AS NewField
FROM
(
    SELECT DISTINCT 
        ProjectID
        ,newID.value AS val
    FROM 
        [dbo].[Data] WITH(NOLOCK)  
        CROSS APPLY STRING_SPLIT([bID],';') AS newID  
    WHERE 
        newID.value IN ('O95833' , 'Q96NY7-2') 
) t
GROUP BY
    ProjectID

应该可以的。

【讨论】:

【参考方案6】:

您可以在用于apply 的子查询中使用distinct

SELECT d.ProjectID,
       STRING_AGG(  newID.value, ',') WITHIN GROUP (ORDER BY newID.value) AS 
NewField
FROM [dbo].[Data] d CROSS APPLY
     (select distinct value
      from STRING_SPLIT(d.[bID], ';') AS newID 
     ) newID
WHERE newID.value IN (   'O95833' , 'Q96NY7-2'  ) 
group by projectid;

【讨论】:

【参考方案7】:

在合并结果之前在子查询中使用DISTINCT 关键字删除重复项:SQL Fiddle

SELECT 
ProjectID
,STRING_AGG(value, ',') WITHIN GROUP (ORDER BY value) AS 
NewField
from (
    select distinct ProjectId, newId.value 
    FROM [dbo].[Data] WITH(NOLOCK)  
    CROSS APPLY STRING_SPLIT([bID],';') AS newID  
    WHERE newID.value IN (   'O95833' , 'Q96NY7-2'  )  
) x
GROUP BY ProjectID
ORDER BY ProjectID

【讨论】:

ps。文档中的几个 cmets 要求 distinct 关键字支持此功能;所以也许 MS 会考虑在未来的版本中添加这样的功能:docs.microsoft.com/en-us/sql/t-sql/functions/… 请在此处为 DISTINCT 投票:feedback.azure.com/forums/908035-sql-server/suggestions/… 更新了投票给 DISTINCT 的链接:feedback.azure.com/d365community/idea/…

以上是关于在 SQL Server 中使用 STRING_AGG 获取唯一值的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 函数错误(我不能在 SQL Server 函数中使用 NewID() 吗?)

在 ASP.NET 中使用 .MDF SQL Server 数据库与使用 SQL Server

Query (SQL Server 2008 Express) 在 SQL Server Management Studio 中有效,但在 Delphi 中使用 ADODB 无效

sql server中如何实现在异地访问数据库

在 sql server 中,使用整数变量构建动态 sql

如何使用sql server数据库中的标量值函数