Por curiosidad (ligeramente morbosa), traté de idear un medio para transformar los datos de entrada exactos que ha proporcionado.
Mucho mejor, por supuesto, sería estructurar adecuadamente los datos originales. Con un sistema heredado, esto puede no ser posible, pero se podría crear un proceso ETL para llevar esta información a una ubicación intermedia, de modo que no sea necesario ejecutar una consulta fea como esta en tiempo real.
Ejemplo #1
Este ejemplo asume que todos los ID son coherentes y secuenciales (de lo contrario, un ROW_NUMBER()
adicional o se necesitaría usar una nueva columna de identidad para garantizar las operaciones de resto correctas en 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
Ejemplo n.º 2:sin dependencia de los ID secuenciales
Este es un ejemplo más práctico porque los valores de ID reales no importan, solo la secuencia de filas.
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;
Ejemplo #3:Cursor
Nuevamente, sería mejor evitar ejecutar una consulta como esta en tiempo real y usar un proceso ETL transaccional programado. En mi experiencia, los datos semiestructurados como este son propensos a anomalías.
Si bien los ejemplos n.º 1 y n.º 2 (y las soluciones proporcionadas por otros) demuestran formas inteligentes de trabajar con los datos, una forma más práctica de transformar estos datos sería un cursor. ¿Por qué? en realidad, puede funcionar mejor (sin consultas anidadas, recursividad, rotación o numeración de filas) e incluso si es más lento, brinda muchas mejores oportunidades para el manejo de errores.
-- 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;
Rendimiento
Creé datos fuente de muestra de 100 000 filas y los tres ejemplos mencionados anteriormente parecen aproximadamente equivalentes para transformar datos.
Creé un millón de filas de datos de origen y una consulta similar a la siguiente ofrece un rendimiento excelente para seleccionar un subconjunto de filas (como las que se usarían en una cuadrícula en una página web o un informe).
-- 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;
Veo tiempos de ejecución de 4 a 10 ms (i7-3960x) en un conjunto de un millón de registros.