Si vous êtes autorisé à utiliser CLR dans votre environnement, il s'agit d'un cas sur mesure pour un agrégat défini par l'utilisateur.
En particulier, c'est probablement la voie à suivre si les données source ne sont pas trivialement grandes et / ou si vous avez besoin de faire ce genre de choses beaucoup dans votre application. Je soupçonne fortement le plan de requête pour la solution d' Aaron pas bien à mesure que la taille d'entrée augmente. (J'ai essayé d'ajouter un index à la table temporaire, mais cela n'a pas aidé.)
Cette solution, comme bien d'autres choses, est un compromis:
- Politique / politique pour même utiliser l'intégration CLR dans votre environnement ou celui de votre client.
- La fonction CLR est probablement plus rapide et évoluera mieux compte tenu d'un ensemble réel de données.
- La fonction CLR sera réutilisable dans d'autres requêtes, et vous n'aurez pas à dupliquer (et déboguer) une sous-requête complexe à chaque fois que vous devez faire ce type de chose.
- Straight T-SQL est plus simple que d'écrire et de gérer un morceau de code externe.
- Peut-être que vous ne savez pas comment programmer en C # ou VB.
- etc.
EDIT: Eh bien, je suis allé essayer de voir si c'était vraiment mieux, et il s'avère que l'exigence que les commentaires soient dans un ordre spécifique n'est actuellement pas possible de satisfaire en utilisant une fonction d'agrégation. :(
Voir SqlUserDefinedAggregateAttribute.IsInvariantToOrder . Fondamentalement, ce que vous devez faire est OVER(PARTITION BY customer_code ORDER BY row_num)
mais ORDER BY
n'est pas pris en charge dans leOVER
clause lors de l'agrégation. Je suppose que l'ajout de cette fonctionnalité à SQL Server ouvre une boîte de vers, car ce qui devrait être modifié dans le plan d'exécution est trivial. Le lien susmentionné indique que cela est réservé pour une utilisation future, donc cela pourrait être implémenté à l'avenir (en 2005, vous n'avez probablement pas de chance, cependant).
Cela pourrait encore être accompli en emballant et en analysantrow_num
valeur dans la chaîne agrégée, puis en effectuant le tri dans l'objet CLR ... ce qui semble assez hackish.
Dans tous les cas, voici le code que j'ai utilisé au cas où quelqu'un d'autre trouverait cela utile même avec la limitation. Je vais laisser la partie de piratage comme un exercice pour le lecteur. Notez que j'ai utilisé AdventureWorks (2005) pour les données de test.
Assemblage d'agrégats:
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 pour tester ( CREATE ASSEMBLY
, et sp_configure
pour activer CLR omis):
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