Concatenação de campos em diferentes linhas
-
20-09-2019 - |
Pergunta
Estou preso a um problema de agregação que não consigo chegar ao fundo.
Eu tenho alguns dados que estão melhor resumidos da seguinte forma
id |phraseId|seqNum|word
=========================
1 |1 |1 |hello
2 |1 |2 |world
3 |2 |1 |black
4 |2 |2 |and
5 |2 |3 |white
Eu gostaria de uma consulta que retribua os seguintes dados:
phraseId|completePhrase
========================
1 |hello world
2 |black and white
Alguém?
EDITAR
Percebo que todas as soluções fornecidas usam FOR XML PATH
. O que é essa mágica?
Solução
Uma solução é criar um Udf usando um Para o caminho XML expressão.
- O UDF lida com a concatenação de uma fraseida
- pode ser usado em uma seleção normal
Declaração SQL
SELECT PhraseID, dbo.UDF_ConcatWord(PhraseID)
FROM Phrases
GROUP BY PhraseID
Criando o UDF
CREATE FUNCTION dbo.UDF_ConcatWord(@phraseID INT) RETURNS VARCHAR(8000) AS
BEGIN
DECLARE @r VARCHAR(8000)
SELECT @r = (
SELECT word + ', '
FROM Phrases
WHERE phraseID = @phraseID
FOR XML PATH('')
)
IF LEN(@r) > 0 SET @r = SUBSTRING(@r, 1, LEN(@r)-1)
RETURN @r
END
GO
editar
Depois de revisar alguns dos links, uma solução ainda mais curta é
Declaração SQL
SELECT DISTINCT p1.PhraseID
, STUFF(( SELECT ' ' + p2.word
FROM Phrases AS p2
WHERE p2.PhraseID = p1.PhraseID
FOR XML PATH('')), 1, 1, '') AS completePhrase
FROM Phrases AS p1
ORDER BY p1.PhraseID
Outras dicas
Experimente isso:
DECLARE @TableA table (RowID int, phraseId varchar(5),seqNum int, word varchar(5))
INSERT INTO @TableA VALUES (1,1,1,'hello')
INSERT INTO @TableA VALUES (2,1,2,'world')
INSERT INTO @TableA VALUES (3,2,1,'black')
INSERT INTO @TableA VALUES (4,2,2,'and')
INSERT INTO @TableA VALUES (5,2,3,'white')
SELECT
c1.phraseId
,STUFF(
(SELECT
' ' + word
FROM @TableA c2
WHERE c2.phraseId=c1.phraseId
ORDER BY c1.phraseId, seqNum
FOR XML PATH('')
)
,1,1, ''
) AS CombinedValue
FROM @TableA c1
GROUP BY c1.phraseId
ORDER BY c1.phraseId
RESULTADO:
phraseId CombinedValue
-------- --------------------------
1 hello world
2 black and white
(2 row(s) affected)
Eu trapaceei um pouco assumindo que você tem uma tabela que mantém o registro do cabeçalho para cada frase. Se isso estiver faltando, você pode construí -lo selecionando uma lista distinta de fraseidas da tabela que contém as palavras:
declare @words table
(id int
,phraseId int
,seqNum int
,word varchar(10)
)
insert @words
select 1,1,1,'hello'
union select 2,1,2,'world'
union select 3,2,1,'black'
union select 4,2,2,'and'
union select 5,2,4,'white'
declare @phrase table
(phraseId int)
insert @phrase
select 1
union select 2
select phraseID
,phraseText AS completePhrase
FROM @phrase AS p
CROSS APPLY (select word + ' ' as [text()]
from @words AS w
where w.phraseID = p.phraseID
for xml path('')
) as phrases (phraseText)
No final, usei a segunda resposta de Lieven, mas descobri que, para certas combinações de cordas, o FOR XML PATH('')
truques causam problemas a ocorrer:
declare @phrases table
(
id int
,phraseId int
,seqNum int
,word varchar(10)
)
insert
@phrases
values
(1,1,1,'hello'),
(2,1,2,'world'),
(3,2,1,'black'),
(4,2,2,'and'),
(5,2,3,'white')
SELECT
DISTINCT p1.PhraseID,
STUFF(
(
SELECT
' ' + p2.word
FROM
@phrases AS p2
WHERE
p2.PhraseID = p1.PhraseID
FOR XML PATH('')
), 1, 1, '') AS completePhrase
FROM
@phrases AS p1
ORDER BY
p1.PhraseID
Funciona bem, mas se o exemplo usa caracteres que precisariam escapar se eles usam em um XML, ocorrem problemas. Por exemplo, executando os seguintes dados através dele:
insert
@words
values
(1,1,1,'hello>'), --notice the less than symbol
(2,1,2,'world'),
(3,2,1,'black')
Dá
hello> world
e também se a tabela de origem for declarada fora de ordem, um order by
É necessário
Um pequeno mod para a consulta original corrige tudo:
SELECT
DISTINCT p1.PhraseID,
STUFF(
(
SELECT
' ' + p2.word
FROM
@words AS p2
WHERE
p2.PhraseID = p1.PhraseID
ORDER BY
p2.seqNum --required
FOR XML PATH(''),TYPE
).value('.','nvarchar(4000)'),
1,
1,
''
) AS completePhrase
FROM
@words AS p1
ORDER BY
p1.PhraseID
(Vejo Para XML Path (''): escapar de caracteres "especiais")