環境でCLRを使用することが許可されている場合、これはユーザー定義集計のテーラーメイドのケースです。
特に、ソースデータが非常に大きくない場合や、アプリケーションでこの種の処理を頻繁に行う必要がある場合に、おそらくこれが適切な方法です。Aaronのソリューションのクエリプランは、入力サイズが大きくなるとうまくスケーリングしないと強く思います。(インデックスを一時テーブルに追加しようとしましたが、助けにはなりませんでした。)
このソリューションは、他の多くのものと同様に、トレードオフです。
- お客様またはお客様の環境でCLR統合を使用するための政治/ポリシー。
- CLR関数は高速である可能性が高く、実際のデータセットがあれば、より適切にスケーリングされます。
- CLR関数は他のクエリで再利用可能であり、この種のことを行う必要があるたびに複雑なサブクエリを複製(およびデバッグ)する必要はありません。
- ストレートT-SQLは、外部コードを記述して管理するよりも簡単です。
- おそらく、C#またはVBでプログラムする方法がわからないでしょう。
- 等
編集:まあ、私はこれが実際に優れているかどうかを確認しようとしましたが、コメントが特定の順序であるという要件は、集約関数を使用して現在満たすことができないことが判明しました。:(
SqlUserDefinedAggregateAttribute.IsInvariantToOrderを参照してください。基本的に、行う必要OVER(PARTITION BY customer_code ORDER BY row_num)
があるのORDER BY
はOVER
、集計時の句でサポートされていません。この機能をSQL Serverに追加すると、実行計画で変更が必要になるのは簡単なため、ワームの缶が開かれると思います。前述のリンクでは、これは将来の使用のために予約されているため、将来実装される可能性があります(ただし、2005年にはおそらく運が悪いでしょう)。
これは、row_num
値を集計文字列にパックして解析し、CLRオブジェクト内で並べ替えを行うことで実現できます。
いずれにせよ、制限がある場合でも他の誰かがこれを便利だと思う場合に使用するコードを以下に示します。ハッキングの部分は読者の演習として残しておきます。テストデータにはAdventureWorks(2005)を使用したことに注意してください。
集合体アセンブリ:
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(CREATE ASSEMBLY
、およびsp_configure
CLRを有効にするには省略):
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