От (леко болезнено) любопитство се опитах да измисля средство за трансформиране на точните входни данни, които сте предоставили.
Много по-добре, разбира се, би било правилно да се структурират оригиналните данни. С наследена система това може да не е възможно, но може да се създаде ETL процес, който да пренесе тази информация в междинно местоположение, така че да не е необходимо грозна заявка като тази да се изпълнява в реално време.
Пример #1
Този пример предполага, че всички идентификатори са последователни и последователни (в противен случай допълнителен ROW_NUMBER()
колона или нова колона за самоличност ще трябва да се използва, за да се гарантират правилни остатъчни операции върху 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 нямат значение, а само последователността на редовете.
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 реда и трите гореспоменати примера изглеждат приблизително еквивалентни за трансформиране на данни.
Създадох милион реда от изходни данни и заявка, подобна на следната, дава отлична производителност за избиране на подмножество от редове (като например, което ще се използва в мрежа на уеб страница или отчет).
-- 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;
Виждам времена за изпълнение от 4-10 ms (i7-3960x) срещу набор от милион записа.