【问题标题】:stuff() adds separator even when fields are empty即使字段为空, stuff() 也会添加分隔符
【发布时间】:2018-10-12 08:44:45
【问题描述】:

请考虑以下陈述:

select stuff((
           select '; ' + ([FIELD_1] + [FIELD_2] + [...] + [FIELD_N])
           from   [TABLE] t1
           where  t1.[ID] = t2.[ID]
           for    xml path ('')
        ),1,1, '')
from    [TABLE] t2

如果语句连接了 10 条具有相同 ID 的记录,但所有记录都没有值('',或为空,而不是 null),则输出为:

; ; ; ; ; ; ; ; ; ;

如果填写了 2 条记录,我会得到

; ; ; AAA; ; ; ; BBB;

在这两种情况下我想要的是null,分别是AAA; BBB

我试过这样修复它:

select stuff((
           select case when [FIELD_1] <> '' then '; ' + ([FIELD_1]) else '' end
           from   [TABLE] t1
           where  t1.[ID] = t2.[ID]
           for    xml path ('')
        ),1,1, '')
from    [TABLE] t2

这很有效,当我只选择FIELD_1 时,这对我来说已经足够优雅了。但是当我选择许多连接字段 (FIELD_1 + ... + FIELD_N) 时,这会变得非常难看。

我做错了什么? stuff() 不应该把我的问题作为一个函数来处理吗?

【问题讨论】:

  • ';' + '' 会给你';'。这是对的。如果要跳过空字符串,请将该条件添加到 WHERE 子句

标签: sql-server tsql sql-server-2012


【解决方案1】:

WHERE 子句中添加条件以排除所有值为空字符串的行

select stuff((
           select '; ' + ([FIELD_1] + [FIELD_2] + [...] + [FIELD_N])
           from   [TABLE] t1
           where  t1.[ID] = t2.[ID]
           and    [FIELD_1] + [FIELD_2] + [...] + [FIELD_N] <> ''
           for    xml path ('')
        ),1,1, '')
from    [TABLE] t2

【讨论】:

    【解决方案2】:

    此答案并非旨在展示更好的查询方式 - 它旨在解释您正在使用的查询。

    要解决您的问题,您应该像his answer 中演示的那样使用where 子句,而不是使用case 表达式。选择你没用的东西是没有意义的。

    我看到很多关于这种字符串聚合方法的困惑 - 太多人在使用它而不了解它的每个部分的作用 - 所以这里是一个简单的解释:

    这项技术分为三个部分:

    1. 聚合列中的值。这就是FOR XML PATH('') 所做的。 FOR XML PATH('') 将返回一个 xml 字符串,其中 xml 标记是 查询的列名(或别名)。 (见cte_ForXmlAggregated 下面的示例脚本中的列)。

    2. 在值之间添加分隔符。这就是';' + 所做的。 它还会从结果中删除列名 XML 标记 - 通过将列名更改为空字符串。空别名在 T-SQL,但通过将另一个值连接到列,列名可以 不再使用。
      请参阅样本上的 cte_ForXml_WithAnEmptyStringOnlyValuesAggregated 脚本。

    3. 删除字符串开头的额外分隔符。这就是 stuff 确实如此。
      请参阅示例脚本中的cte_FirstDlimiterRemovedFullQuery

    stuff 所做的是将一个字符串插入另一个字符串,在指定的index 处,同时从原始字符串中删除length 字符。如果您要插入一个空字符串,您只需从原始字符串中删除由indexlength 参数指定的部分。

    示例脚本来说明这种字符串聚合技术的不同步骤:

    DECLARE @String_Agg AS TABLE 
    (
        s varchar(10)
    )
    
    INSERT INTO @String_Agg (s) VALUES
    ('1'), ('2'), ('3')
    
    ;WITH cte_ForXml(Aggregated) AS
    (
        SELECT s
        FROM @String_Agg
        FOR XML PATH('')
    )
    , cte_ForXml_WithAnEmptyString(OnlyValuesAggregated) AS
    (
        SELECT '' + s
        FROM @String_Agg
        FOR XML PATH('')
    ) 
    , cte_ForXmlWithADelimiter(AggregatedWithADlimiter) AS
    (
        SELECT ';' + s
        FROM @String_Agg
        FOR XML PATH('')
    ), cte_FirstDlimiterRemoved(FullQuery) AS
    (
        SELECT STUFF
        (
            (
                SELECT ';' + s
                FROM @String_Agg
                FOR XML PATH('')
            ), 1, 1, ''
        )
    )
    
    SELECT Aggregated,  OnlyValuesAggregated, AggregatedWithADlimiter, FullQuery
    FROM cte_ForXml
    CROSS JOIN cte_ForXml_WithAnEmptyString
    CROSS JOIN cte_ForXmlWithADelimiter
    CROSS JOIN cte_FirstDlimiterRemoved
    

    结果:

    Aggregated                  OnlyValuesAggregated    AggregatedWithADlimiter     FullQuery
    <s>1</s><s>2</s><s>3</s>    123                     ;1;2;3                      1;2;3
    

    【讨论】:

    • 没错。 stuff() 是一种方便的方式。另一种方法是使用substring()len()
    • substringlen 的问题是您需要两次实际字符串 - 意味着要么将 for xml 查询的值放入变量中,要么重复整个查询 - 两种方式与简单地使用stuff相比太麻烦了。
    猜你喜欢
    • 2014-05-15
    • 2014-11-26
    • 2021-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-18
    • 2021-12-30
    相关资源
    最近更新 更多