Par curiosité (un peu morbide), j'ai essayé de trouver un moyen de transformer les données d'entrée exactes que vous avez fournies.
Le mieux, bien sûr, serait de structurer correctement les données d'origine. Avec un ancien système, cela peut ne pas être possible, mais un processus ETL pourrait être créé pour amener ces informations dans un emplacement intermédiaire afin qu'une requête laide comme celle-ci n'ait pas besoin d'être exécutée en temps réel.
Exemple #1
Cet exemple suppose que tous les identifiants sont cohérents et séquentiels (sinon, un ROW_NUMBER()
supplémentaire colonne ou une nouvelle colonne d'identité devrait être utilisée pour garantir des opérations de reste correctes sur l'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
Exemple n° 2 :aucune dépendance vis-à-vis des identifiants séquentiels
Il s'agit d'un exemple plus pratique car les valeurs d'ID réelles n'ont pas d'importance, seule la séquence de lignes.
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;
Exemple #3 :Curseur
Encore une fois, il serait préférable d'éviter d'exécuter une requête comme celle-ci en temps réel et d'utiliser un processus ETL transactionnel planifié. D'après mon expérience, des données semi-structurées comme celle-ci sont sujettes à des anomalies.
Alors que les exemples #1 et #2 (et les solutions fournies par d'autres) montrent des façons intelligentes de travailler avec les données, un moyen plus pratique de transformer ces données serait un curseur. Pourquoi? il peut en fait mieux fonctionner (pas de requêtes imbriquées, de récursivité, de pivotement ou de numérotation des lignes) et même s'il est plus lent, il offre de bien meilleures possibilités de gestion des erreurs.
-- 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;
Performances
J'ai créé des exemples de données source de 100 000 lignes et les trois exemples susmentionnés semblent à peu près équivalents pour la transformation des données.
J'ai créé un million de lignes de données source et une requête similaire à la suivante donne d'excellentes performances pour sélectionner un sous-ensemble de lignes (comme celles qui seraient utilisées dans une grille sur une page Web ou un rapport).
-- 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;
Je vois des temps d'exécution de 4 à 10 ms (i7-3960x) contre un ensemble d'un million d'enregistrements.