(やや病的な) 好奇心から、あなたが提供した正確な入力データを変換する手段を考え出そうとしました.
もちろん、元のデータを適切に構造化する方がはるかに優れています。従来のシステムでは、これは不可能かもしれませんが、ETL プロセスを作成してこの情報を中間の場所に移動し、このような醜いクエリをリアルタイムで実行する必要がないようにすることができます。
例 #1
この例では、すべての ID が一貫しており、連続していることを前提としています (そうでない場合、追加の ROW_NUMBER()
ID に対する正しい剰余演算を保証するには、列または新しい ID 列を使用する必要があります)。
SELECT
Name = REPLACE( Name, 'name: ', '' ),
Age = REPLACE( Age, 'age: ', '' )
FROM
(
SELECT
Name = T2.Data,
Age = T1.Data,
RowNumber = ROW_NUMBER() OVER( ORDER BY T1.Id ASC )
FROM @t T1
INNER JOIN @t T2 ON T1.id = T2.id +1 -- offset by one to combine two rows
WHERE T1.id % 3 != 0 -- skip delimiter records
) Q1
-- skip every other record (minus delimiters, which have already been stripped)
WHERE RowNumber % 2 != 0
例 #2:シーケンシャル ID に依存しない
実際の ID 値は重要ではなく、行の順序のみが重要であるため、これはより実用的な例です。
DECLARE @NumberedData TABLE( RowNumber INT, Data VARCHAR( 100 ) );
INSERT @NumberedData( RowNumber, Data )
SELECT
RowNumber = ROW_NUMBER() OVER( ORDER BY id ASC ),
Data
FROM @t;
SELECT
Name = REPLACE( N2.Data, 'name: ', '' ),
Age = REPLACE( N1.Data, 'age: ', '' )
FROM @NumberedData N1
INNER JOIN @NumberedData N2 ON N1.RowNumber = N2.RowNumber + 1
WHERE ( N1.RowNumber % 3 ) = 2;
DELETE @NumberedData;
例 #3:カーソル
繰り返しになりますが、このようなクエリをリアルタイムで実行することは避け、スケジュールされたトランザクション ETL プロセスを使用することをお勧めします。私の経験では、このような半構造化データは異常になりやすいです。
例 1 と 2 (および他の人が提供する解決策) は、データを操作する巧妙な方法を示していますが、このデータを変換するより実用的な方法はカーソルです。なんで?実際にはパフォーマンスが向上する可能性があり (ネストされたクエリ、再帰、ピボット、または行番号付けがない)、低速であってもエラー処理の機会が大幅に改善されます。
-- this could be a table variable, temp table, or staging table
DECLARE @Results TABLE ( Name VARCHAR( 100 ), Age INT );
DECLARE @Index INT = 0, @Data VARCHAR( 100 ), @Name VARCHAR( 100 ), @Age INT;
DECLARE Person_Cursor CURSOR FOR SELECT Data FROM @t;
OPEN Person_Cursor;
FETCH NEXT FROM Person_Cursor INTO @Data;
WHILE( 1 = 1 )BEGIN -- busy loop so we can handle the iteration following completion
IF( @Index = 2 ) BEGIN
INSERT @Results( Name, Age ) VALUES( @Name, @Age );
SET @Index = 0;
END
ELSE BEGIN
-- optional: examine @Data for integrity
IF( @Index = 0 ) SET @Name = REPLACE( @Data, 'name: ', '' );
IF( @Index = 1 ) SET @Age = CAST( REPLACE( @Data, 'age: ', '' ) AS INT );
SET @Index = @Index + 1;
END
-- optional: examine @Index to see that there are no superfluous trailing
-- rows or rows omitted at the end.
IF( @@FETCH_STATUS != 0 ) BREAK;
FETCH NEXT FROM Person_Cursor INTO @Data;
END
CLOSE Person_Cursor;
DEALLOCATE Person_Cursor;
パフォーマンス
100K 行のサンプル ソース データを作成しましたが、前述の 3 つの例は、データの変換に関してはほぼ同等のようです。
100 万行のソース データを作成しました。次のようなクエリを使用すると、行のサブセットを選択する際に優れたパフォーマンスが得られます (Web ページまたはレポートのグリッドで使用される場合など)。
-- INT IDENTITY( 1, 1 ) numbers the rows for us
DECLARE @NumberedData TABLE( RowNumber INT IDENTITY( 1, 1 ), Data VARCHAR( 100 ) );
-- subset selection; ordering/filtering can be done here but it will need to preserve
-- the original 3 rows-per-result structure and it will impact performance
INSERT @NumberedData( Data )
SELECT TOP 1000 Data FROM @t;
SELECT
N1.RowNumber,
Name = REPLACE( N2.Data, 'name: ', '' ),
Age = REPLACE( N1.Data, 'age: ', '' )
FROM @NumberedData N1
INNER JOIN @NumberedData N2 ON N1.RowNumber = N2.RowNumber + 1
WHERE ( N1.RowNumber % 3 ) = 2;
DELETE @NumberedData;
100 万件のレコード セットに対して 4 ~ 10 ミリ秒 (i7-3960x) の実行時間が見られます。