Si puede usar CLR en su entorno, este es un caso personalizado para un agregado definido por el usuario.
En particular, este es probablemente el camino a seguir si los datos de origen no son trivialmente grandes y / o necesita hacer mucho este tipo de cosas en su aplicación. Sospecho firmemente que el plan de consulta para la solución de Aaron no escalará bien a medida que aumente el tamaño de entrada. (Intenté agregar un índice a la tabla temporal, pero eso no ayudó).
Esta solución, como muchas otras cosas, es una compensación:
- Política / política para incluso usar CLR Integration en su entorno o el de su cliente.
- La función CLR es probablemente más rápida y escalará mejor dado un conjunto real de datos.
- La función CLR será reutilizable en otras consultas, y no tendrá que duplicar (y depurar) una subconsulta compleja cada vez que necesite hacer este tipo de cosas.
- Straight T-SQL es más simple que escribir y administrar un fragmento de código externo.
- Quizás no sabes cómo programar en C # o VB.
- etc.
EDITAR: Bueno, fui a tratar de ver si esto era realmente mejor, y resulta que el requisito de que los comentarios estén en un orden específico actualmente no es posible satisfacerlos usando una función agregada. :(
Ver SqlUserDefinedAggregateAttribute.IsInvariantToOrder . Básicamente, lo que debe hacer es OVER(PARTITION BY customer_code ORDER BY row_num)
pero ORDER BY
no se admite en la OVER
cláusula al agregar. Supongo que agregar esta funcionalidad a SQL Server abre una lata de gusanos, porque lo que debería cambiarse en el plan de ejecución es trivial. El enlace mencionado anteriormente dice que está reservado para uso futuro, por lo que esto podría implementarse en el futuro (sin embargo, en 2005 probablemente no tenga suerte).
Esto podría todavía ser realizado por el embalaje y analizar el row_num
valor agregado en la cadena, y luego hacer el tipo dentro del objeto CLR ... que parece bastante hacker.
En cualquier caso, a continuación se muestra el código que usé en caso de que alguien más lo encuentre útil, incluso con la limitación. Dejaré la parte de piratería como un ejercicio para el lector. Tenga en cuenta que utilicé AdventureWorks (2005) para los datos de prueba.
Asamblea 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 probar ( CREATE ASSEMBLY
y sp_configure
para habilitar 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