Se você tem permissão para usar o CLR em seu ambiente, este é um caso personalizado para um agregado definido pelo usuário.
Em particular, esse é provavelmente o caminho a seguir se os dados de origem não forem trivialmente grandes e / ou você precisar fazer muito esse tipo de coisa em seu aplicativo. Suspeito fortemente que o plano de consulta da solução de Aaron não seja dimensionado bem à medida que o tamanho da entrada aumentar. (Tentei adicionar um índice à tabela temporária, mas isso não ajudou.)
Essa solução, como muitas outras coisas, é uma troca:
- Política / política para usar o CLR Integration no seu ambiente ou no do seu cliente.
- A função CLR é provavelmente mais rápida e será melhor dimensionada, considerando um conjunto real de dados.
- A função CLR será reutilizável em outras consultas e você não precisará duplicar (e depurar) uma subconsulta complexa toda vez que precisar fazer esse tipo de coisa.
- O T-SQL direto é mais simples do que escrever e gerenciar um pedaço de código externo.
- Talvez você não saiba como programar em C # ou VB.
- etc.
Edição: Bem, fui tentar ver se isso realmente era melhor, e verifica-se que o requisito de que os comentários estejam em uma ordem específica não é atualmente possível satisfazer usando uma função agregada. :(
Consulte SqlUserDefinedAggregateAttribute.IsInvariantToOrder . Basicamente, o que você precisa fazer é, OVER(PARTITION BY customer_code ORDER BY row_num)
mas ORDER BY
não é suportado na OVER
cláusula ao agregar. Suponho que adicionar essa funcionalidade ao SQL Server abra uma lata de worms, porque o que precisaria ser alterado no plano de execução é trivial. O link mencionado acima diz que isso é reservado para uso futuro; portanto, isso pode ser implementado no futuro (em 2005, você provavelmente está sem sorte).
Isso ainda pode ser feito compactando e analisando o row_num
valor na cadeia de caracteres agregada e, em seguida, fazendo a classificação dentro do objeto CLR ... o que parece bastante tolo.
De qualquer forma, abaixo está o código que eu usei caso alguém ache isso útil, mesmo com a limitação. Vou deixar a parte de hackers como um exercício para o leitor. Observe que eu usei o AdventureWorks (2005) para dados de teste.
Montagem agregada:
using System;
using System.IO;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
namespace MyCompany.SqlServer
{
[Serializable]
[SqlUserDefinedAggregate
(
Format.UserDefined,
IsNullIfEmpty = false,
IsInvariantToDuplicates = false,
IsInvariantToNulls = true,
IsInvariantToOrder = false,
MaxByteSize = -1
)]
public class StringConcatAggregate : IBinarySerialize
{
private string _accum;
private bool _isEmpty;
public void Init()
{
_accum = string.Empty;
_isEmpty = true;
}
public void Accumulate(SqlString value)
{
if (!value.IsNull)
{
if (!_isEmpty)
_accum += ' ';
else
_isEmpty = false;
_accum += value.Value;
}
}
public void Merge(StringConcatAggregate value)
{
Accumulate(value.Terminate());
}
public SqlString Terminate()
{
return new SqlString(_accum);
}
public void Read(BinaryReader r)
{
this.Init();
_accum = r.ReadString();
_isEmpty = _accum.Length == 0;
}
public void Write(BinaryWriter w)
{
w.Write(_accum);
}
}
}
T-SQL para teste ( CREATE ASSEMBLY
e sp_configure
para ativar o CLR omitido):
CREATE TABLE [dbo].[Comments]
(
CustomerCode int NOT NULL,
RowNum int NOT NULL,
Comments nvarchar(25) NOT NULL
)
INSERT INTO [dbo].[Comments](CustomerCode, RowNum, Comments)
SELECT
DENSE_RANK() OVER(ORDER BY FirstName),
ROW_NUMBER() OVER(PARTITION BY FirstName ORDER BY ContactID),
Phone
FROM [AdventureWorks].[Person].[Contact]
GO
CREATE AGGREGATE [dbo].[StringConcatAggregate]
(
@input nvarchar(MAX)
)
RETURNS nvarchar(MAX)
EXTERNAL NAME StringConcatAggregate.[MyCompany.SqlServer.StringConcatAggregate]
GO
SELECT
CustomerCode,
[dbo].[StringConcatAggregate](Comments) AS AllComments
FROM [dbo].[Comments]
GROUP BY CustomerCode