【问题标题】:Get unique values using STRING_AGG in SQL Server在 SQL Server 中使用 STRING_AGG 获取唯一值
【发布时间】:2024-05-20 10:30:02
【问题描述】:

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

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,因为不幸的是,原始数据存储为您意识到的分隔值。

标签: sql sql-server sql-server-2017 string-aggregation


【解决方案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 获取唯一字符串的另一种可能性是在获取逗号分隔的字符串后执行这三个步骤:

    1. 拆分字符串 (STRING_SPLIT)
    2. 从拆分中选择DISTINCT
    3. 再次将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
            

            【讨论】: