web-dev-qa-db-ja.com

SQL列の区切り値を複数の行に分割する

Exchange 2007からSQLにメッセージ追跡ログを挿入する際に作業している背景情報を提供するために、ここでいくつかのアドバイスを本当にお願いします。 1日あたり数百万の行があるため、Bulk Insertステートメントを使用してデータをSQLテーブルに挿入しています。

実際、私は実際に一時テーブルに一括挿入し、そこからライブテーブルにデータをマージします。これは、特定のフィールドに値の周りに引用符などがあるため、テスト解析の問題のためです。

これは、recipient-address列が;で区切られた区切りフィールドであるという事実を除いて、うまく機能します。多くのメール受信者が存在する可能性があるため、非常に長い場合があります。

この列を取得し、値を複数の行に分割してから、別のテーブルに挿入します。問題は、私がしようとしていることは、時間がかかりすぎているか、私が望むように動作していないことです。

このデータ例をご覧ください。

message-id                                              recipient-address
[email protected]   [email protected]
[email protected]     [email protected]
[email protected]              [email protected];[email protected];[email protected]

Recipientsテーブルで次のようにフォーマットすることを希望します。

message-id                                              recipient-address
[email protected]   [email protected]
[email protected]     [email protected]
[email protected]              [email protected]
[email protected]              [email protected]
[email protected]              [email protected]

誰も私がこれを行う方法についてアイデアを持っていますか?

PowerShellをよく知っているので、その中で試してみましたが、28Kレコードのforeachループでも処理に時間がかかりました。できる限り迅速に/効率的に実行できるものが必要です。

ありがとう!

16
HungryHippos

最初に、分割関数を作成します。

CREATE FUNCTION dbo.SplitStrings
(
    @List       NVARCHAR(MAX),
    @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
AS
    RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number),
        Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@List, Number, 
        CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)))
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id])
        FROM sys.all_objects AS s1 CROSS APPLY sys.all_objects) AS n(Number)
    WHERE Number <= CONVERT(INT, LEN(@List))
        AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
    ) AS y);
GO

次の方法で簡単に外挿できます。

SELECT s.[message-id], f.Item
  FROM dbo.SourceData AS s
  CROSS APPLY dbo.SplitStrings(s.[recipient-address], ';') as f;

また、列名にダッシュを入れないことをお勧めします。つまり、常に[square brackets]に配置する必要があります。

48
Aaron Bertrand

SQL Server 2016には、以前のソリューションと同様の新しいテーブル関数string_split()が含まれています。

唯一の要件は、互換性レベルを130に設定することです(SQL Server 2016)

4
Oscar Perez

CROSS APPLY (SQL Server 2005以降で使用可能)および STRING_SPLIT 関数(SQL Server 2016以降で使用可能)を使用できます。

DECLARE @delimiter nvarchar(255) = ';';

-- create tables
CREATE TABLE MessageRecipients (MessageId int, Recipients nvarchar(max));
CREATE TABLE MessageRecipient (MessageId int, Recipient nvarchar(max));

-- insert data
INSERT INTO MessageRecipients VALUES (1, '[email protected]; [email protected]; [email protected]');
INSERT INTO MessageRecipients VALUES (2, '[email protected]; [email protected]');

-- insert into MessageRecipient
INSERT INTO MessageRecipient
SELECT MessageId, ltrim(rtrim(value))
FROM MessageRecipients 
CROSS APPLY STRING_SPLIT(Recipients, @delimiter)

-- output results
SELECT * FROM MessageRecipients;
SELECT * FROM MessageRecipient;

-- delete tables
DROP TABLE MessageRecipients;
DROP TABLE MessageRecipient;

結果:

MessageId   Recipients
----------- ----------------------------------------------------
1           [email protected]; [email protected]; [email protected]
2           [email protected]; [email protected]

そして

MessageId   Recipient
----------- ----------------
1           [email protected]
1           [email protected]
1           [email protected]
2           [email protected]
2           [email protected]
1
Alex Vazhev