【问题标题】:CONCAT_WS() for SQL ServerSQL Server 的 CONCAT_WS()
【发布时间】:2022-04-25 14:28:38
【问题描述】:

如何在 SQL Server 中模拟 MySQL 的 CONCAT_WS() 函数?

这个函数类似于CONCAT() function in SQL Server 2012,只是它在非NULL项之间添加了一个分隔符:

SELECT id, CONCAT_WS('; ', a, b, c, d) AS bar
FROM foo
ORDER BY id;
| ID | BAR        |
|----|------------|
|  1 | a; b; c; d |
|  2 | b; d       |
|  3 | a; d       |
|  4 |            |

(MySQL Fiddle)

【问题讨论】:

  • 我问了这个问题是为了自己回答这个问题并将信息提供给任何人。 (抱歉,如果我找不到合适的问题。)我很乐意通过更好的提示投票/接受其他答案。

标签: sql sql-server tsql


【解决方案1】:

SQL Server 2017 (14.x) 及更高版本具有native CONCAT_WS function

对于旧版本,我们可以使用一些技巧:

  • 跳过NULL 值:COALESCE()
  • 为避免尾随分隔符:在每个项目之前添加它,然后删除第一个,例如STUFF()

他是working example

CREATE TABLE foo (
  id INT IDENTITY(1, 1) NOT NULL,
  a VARCHAR(50),
  b VARCHAR(50),
  c VARCHAR(50),
  d VARCHAR(50),
  PRIMARY KEY (id)
);

INSERT INTO foo (a, b, c, d) VALUES ('a', 'b', 'c', 'd');
INSERT INTO foo (a, b, c, d) VALUES (NULL, 'b', NULL, 'd');
INSERT INTO foo (a, b, c, d) VALUES ('a', NULL, NULL, 'd');
INSERT INTO foo (a, b, c, d) VALUES (NULL, NULL, NULL, NULL);
SELECT id,
STUFF(
    COALESCE('; ' + a, '') +
    COALESCE('; ' + b, '') +
    COALESCE('; ' + c, '') +
    COALESCE('; ' + d, ''),
1, 2, '') AS bar
FROM foo
ORDER BY id
| ID | BAR        |
|----|------------|
|  1 | a; b; c; d |
|  2 | b; d       |
|  3 | a; d       |
|  4 | (null)     |

STUFF(..., 1, 2, '') 的目的是删除初始分隔符(2 在我们的例子中是分隔符长度)。

这应该适用于 SQL Server 2005(可能还有更早的版本)。

注意:与原来的CONCAT_WS()不同,我们的版本在所有项目都是NULL时返回NULL。老实说,我认为这是一个更好的选择,但无论如何它应该很容易改变。

【讨论】:

  • 我认为 CONCAT_WS 从 2017 年或 2019 年开始可用。不知道 STUFF 功能。
  • 这有一个问题。当其中一列为空时,它将始终在末尾/开头添加空格。
  • @Konrad 您能否详细说明空格问题?这个问题是很久以前的事了,但我相信我的测试用例已经涵盖了:-?
  • 例如当您连接 2 个 varchar 列/字符串并且一列为空时,开始/结束时将有多余的空间。所以有必要将这个调用包装在 RTRIM(LTRIM 中以获得正确的结果。
  • 但 CONCAT_WS 也是如此
【解决方案2】:

另一种方法是use a FOR XML subquery,如下所示:

SELECT
  id,
  bar = STUFF(
    (
      SELECT '; ' + v
      FROM (VALUES (a), (b), (c), (d)) AS v (v)
      FOR XML PATH (''), TYPE
    ).value('.[1]', 'varchar(max)'),
    1, 2, ''
  )
FROM foo
ORDER BY id;

一方面,这看起来肯定比一系列 COALESCE 调用更复杂。另一方面,这更接近原型,因为分隔符只指定一次。

使用的语法至少需要 SQL Server 2008+,但如果将 VALUES 构造函数更改为

SELECT a UNION ALL
SELECT b UNION ALL
SELECT c UNION ALL
SELECT d

查询也将在 SQL Server 2005 中运行。

【讨论】:

  • 非常感谢。它在我无法将 SQL Server 升级到 2017 或更高版本的情况下有所帮助,并且 JasperReport 没有提供在跳过空值时连接字符串的智能方法。
【解决方案3】:

SQL Server 2017 开始,您可以使用内置的CONCAT_WS

CONCAT_WS

将可变数量的参数与第一个参数中指定的分隔符连接起来。 (CONCAT_WS 表示用分隔符连接。)

CONCAT_WS ( separator, argument1, argument1 [, argumentN]… ) 

NULL 值的处理

CONCAT_WS 忽略 SET CONCAT_NULL_YIELDS_NULL {ON|OFF} 设置。

如果所有参数都为空,则为 varchar(1) 类型的空字符串 返回。

空值在连接过程中被忽略,并且不添加 分隔器。这有助于连接的常见场景 通常具有空白值的字符串,例如第二个地址字段。 参见示例 B。

如果您的方案需要在分隔符中包含空值, 请参见使用 ISNULL 函数的示例 C。

所以你可以使用你的初始查询:

SELECT id, CONCAT_WS('; ', a, b, c, d) AS bar
FROM foo
ORDER BY id;

db<>fiddle demo

【讨论】:

    【解决方案4】:

    对于 SQL Server 2012,您可以通过将过多的 COALESCE 替换为单个 CONCAT 来简化接受的答案:

    WITH tests(a, b, c, d) AS (
        SELECT NULL, NULL, NULL, NULL UNION
        SELECT NULL, NULL, NULL,  'd' UNION
        SELECT NULL, NULL,  'c', NULL UNION
        SELECT NULL, NULL,  'c',  'd' UNION
        SELECT NULL,  'b', NULL, NULL UNION
        SELECT NULL,  'b', NULL,  'd' UNION
        SELECT NULL,  'b',  'c', NULL UNION
        SELECT NULL,  'b',  'c',  'd' UNION
        SELECT  'a', NULL, NULL, NULL UNION
        SELECT  'a', NULL, NULL,  'd' UNION
        SELECT  'a', NULL,  'c', NULL UNION
        SELECT  'a', NULL,  'c',  'd' UNION
        SELECT  'a',  'b', NULL, NULL UNION
        SELECT  'a',  'b', NULL,  'd' UNION
        SELECT  'a',  'b',  'c', NULL UNION
        SELECT  'a',  'b',  'c',  'd'
    )
    SELECT a, b, c, d,
    STUFF(CONCAT(
        '; ' + a,
        '; ' + b,
        '; ' + c,
        '; ' + d
    ), 1, 2, '') AS cat
    FROM tests
    
    a    | b    | c    | d    | cat
    -----+------+------+------+-----------
    NULL | NULL | NULL | NULL | NULL
    NULL | NULL | NULL | d    | d
    NULL | NULL | c    | NULL | c
    NULL | NULL | c    | d    | c; d
    NULL | b    | NULL | NULL | b
    NULL | b    | NULL | d    | b; d
    NULL | b    | c    | NULL | b; c
    NULL | b    | c    | d    | b; c; d
    a    | NULL | NULL | NULL | a
    a    | NULL | NULL | d    | a; d
    a    | NULL | c    | NULL | a; c
    a    | NULL | c    | d    | a; c; d
    a    | b    | NULL | NULL | a; b
    a    | b    | NULL | d    | a; b; d
    a    | b    | c    | NULL | a; b; c
    a    | b    | c    | d    | a; b; c; d
    

    【讨论】:

    • 这种方法的主要缺点之一是如果值是空字符串而不是null
    • CONCAT_WS 不会跳过空字符串,这样做也是如此。
    • 当然。我只是说一般。最完整的方法是将值包装在 IIF 中以避免尾随字符(在这种情况下为分号)。
    【解决方案5】:

    我用 FOR XML PATH 来做。
    您可以使用联合 (UNION ALL) 代替 VALUES;这具有它仍然适用于 SQL-Server 2005 的附加值(我们仍然必须在我们公司中支持它),并且您可以删除 NULL 值。

    DECLARE @in_SearchTerm1 nvarchar(100) 
    DECLARE @in_SearchTerm2 nvarchar(100) 
    DECLARE @in_SearchTerm3 nvarchar(100) 
    DECLARE @in_SearchTerm4 nvarchar(100) 
    
    SET @in_SearchTerm1 = N'a'
    SET @in_SearchTerm2 = N''
    SET @in_SearchTerm3 = N'c'
    SET @in_SearchTerm4 = N''
    
    SELECT 
        COALESCE
        (
            STUFF
            (
                (
                    SELECT ' / ' + RPT_SearchTerm AS [text()]
                    FROM 
                    (
                                      SELECT NULLIF(@in_SearchTerm1, N'') AS RPT_SearchTerm, 1 AS RPT_Sort 
                            UNION ALL SELECT NULLIF(@in_SearchTerm2, N'') AS RPT_SearchTerm, 2 AS RPT_Sort  
                            UNION ALL SELECT NULLIF(@in_SearchTerm3, N'') AS RPT_SearchTerm, 3 AS RPT_Sort 
                            UNION ALL SELECT NULLIF(@in_SearchTerm4, N'') AS RPT_SearchTerm, 4 AS RPT_Sort 
                    ) AS tempT 
                    WHERE RPT_SearchTerm IS NOT NULL 
                    ORDER BY RPT_Sort 
                    FOR XML PATH(N''), TYPE 
                ).value('.', 'nvarchar(MAX)') 
                ,1
                ,3
                ,N''
            )
            ,N''
        ) AS RPT_SearchTerms 
    

    注意 nvarchar 的使用 - 已经停止使用 varchar。
    您还必须订购它,以保持顺序。


    那么这是做什么的:

    目标:
    获取报告中 4 个单独过滤器中输入的 4 个搜索词。
    在由' / ' 连接的报告中显示这4 个搜索词。
    如果搜索词为空,则不应有 ' / / '
    它应该按顺序显示,即 term1/term2/term3/term4,而不是 e.g.学期4/学期2/学期3/学期1。

    如何:
    因此,您将 4 个搜索词合并,并添加一个排序值以保留 order 。

    你从联合中选择搜索词和分隔符(separatur + null = null)

    SELECT ' / ' + RPT_SearchTerm 
    FROM (UNION OF SEARCH TEMRS) AS tempT
    

    按 RPT_Sort 排序

    现在将 tempT 中的所有值(分隔符 + 文本)选择到一个 XML 文件 (FOR XML) 中,其中所有值都是带有空标记名 (PATH(N'')) 的 XML 元素,然后选择值 XML-text ( AS [text()])(又名 element.innerXML)。

    将其结果作为 XML 元素 (TYPE) 并检索该 XML 元素 (.value('.', 'nvarchar(MAX)')) 的 innerText 字符串(又名 XML 解码)。

    最后,去掉前面的'/'(STUFF(var, 1,3, N'')

    这在原理上完全一样

    CONCAT_WS(' / ', @in_SearchTerm1, @in_SearchTerm2, @in_SearchTerm3, @in_SearchTerm4)
    

    现在添加 nullif,

    CONCAT_WS(' / ', NULLIF(@in_SearchTerm1, '') , NULLIF(@in_SearchTerm2, ''), NULLIF(@in_SearchTerm3, ''), NULLIF(@in_SearchTerm4, ''))
    

    你就在那里。

    这就是您仍然能够在 SQL-Server 中执行 CONCAT_WS 的方式...

    【讨论】:

    • 这与 Andriy M 的回答不同吗? (抱歉,我已经三年没看这个了,我的脑子还没有完全成型。)
    • @Álvaro González:我从谷歌登陆到某个答案。不幸的是没有向上滚动,所以我没有看到它。至于区别:是的,它使用nvarchar,这更好,因为它实际上适用于所有语言)))它还使用NULLIF,这意味着空字符串被删除(不仅仅是空值)。恕我直言,这更有意义。对于不了解 Andriy 所指 UNION ALL 的初学者来说,这可能会更好。所以我不会删除帖子。
    • 很公平。至于空字符串,我认为整个概念是有缺陷的,它们不应该达到高级语言(我们没有空数字或空日期,是吗?)但因为它们实际上在那里,我不认为它是将它们作为 NULL 处理在技术上是正确的(尽管 Oracle 确实如此),但这主要是一个见仁见智的问题,与问题本身并没有太大关系。
    • 我终于花了一些时间来审查您的代码,并且(不是 T-SQL 专家)恐怕我无法弄清楚。我不清楚如何将表注入到您的查询中,或者那些硬编码的@in_SearchTerm... 变量扮演什么角色。如果你有时间,我会很感激更新。谢谢!
    • @Álvaro González :可能有点晚了,但为您添加了解释。
    【解决方案6】:

    我知道这是旧帖子,但我遇到了同样的问题。

    我只是为此使用 CONCAT() 函数。

    我在各个字段中保存了地址行,我想加入所有行来创建地址。

    我发现 CONCAT() 函数可以处理 NULL 并将其替换为空字符串。 如果任何东西加上 NULL 也是 NULL。

    所以我使用常规的 CONCAT() 函数并在每个地址行的末尾添加空格,因此如果该行为 NULL,则组合输出为 null

    SELECT 
        CONCAT(Address01 + ' ', Address02 + ' ', Address03 + ' ', Address04) AS Address 
    FROM myTable
    

    【讨论】:

    • 有趣...唯一的问题是当Address04NULL 并且至少以前的不是时,你会得到一个假的尾随分隔符,不是吗?
    • 是的,但我们可以修剪它
    • 如果分隔符连接在字符串的开头而不是结尾,那么尾随空格不会有问题。 CONCAT(Address01, ' ' + Address02, ' ' + Address03, ' ' + Address04)
    猜你喜欢
    • 2021-10-31
    • 1970-01-01
    • 2016-07-08
    • 2015-01-06
    • 2011-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多