问题
Please consider the following statement:
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
If the statement concatenates 10 records with the same ID
but all records have no value ('', or empty, instead of null
), the output is:
; ; ; ; ; ; ; ; ; ;
If 2 records are filled, I end up with
; ; ; AAA; ; ; ; BBB;
What I would want in these 2 cases is null
, respectively AAA; BBB
I tried fixing it like this:
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
This works, and is elegantly enough for me when I'm only selecting FIELD_1
. But when I'm selecting many concatinated fields (FIELD_1 + ... + FIELD_N
), this becomes quite ugly fast.
What am I doing wrong? Isn't stuff()
supposed to take care of my problem as a function?
回答1:
add a condition to the WHERE
clause to exclude rows with all value empty string
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:
This answer is not intended to show a better way to do the query - it is intended to explain the query you are using.
To solve your problem you should use the where clause as squirrel demonstrated in his answer, instead of using the case
expression. No point of selecting things you have no use for.
I see a lot of confusion about this method of string aggregation - too many people are using it without understanding what each part of it does - so here is a simple explanation:
There are three parts to this technique:
aggregate the values in the columns. That's what the
FOR XML PATH('')
does. TheFOR XML PATH('')
will return an xml string where the xml tags are the column name (or aliases) of the query. (seecte_ForXml
andAggregated
column in the sample script below).Add a delimiter between the values. That's what the
';' +
does. It also removes the column name XML tags from the results - by changing the column name to an empty string. Empty aliases are invalid in T-SQL, but by concatenating another value to the column the column name can no longer be used.
Seecte_ForXml_WithAnEmptyString
andOnlyValuesAggregated
on the sample script.Remove the extra delimiter at the beginning of the string. That's what the
stuff
does.
Seecte_FirstDlimiterRemoved
andFullQuery
in the sample script.
What stuff
does is to insert a string into another string, at a specified index
, while removing length
charecters from the original string. If you are inserting an empty string, you are simply removing the part specified by the index
and length
parameters from the original string.
Sample script to illustrate the different steps of this string aggregation technique:
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
Results:
Aggregated OnlyValuesAggregated AggregatedWithADlimiter FullQuery
<s>1</s><s>2</s><s>3</s> 123 ;1;2;3 1;2;3
来源:https://stackoverflow.com/questions/52775632/stuff-adds-separator-even-when-fields-are-empty