Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come modificare le informazioni in questa tabella in un modulo facile da usare?

Per curiosità (leggermente morbosa) ho cercato di trovare un mezzo per trasformare i dati di input esatti che hai fornito.

Molto meglio, ovviamente, sarebbe strutturare adeguatamente i dati originali. Con un sistema legacy, ciò potrebbe non essere possibile, ma è possibile creare un processo ETL per portare queste informazioni in una posizione intermedia in modo che una brutta query come questa non debba essere eseguita in tempo reale.

Esempio n. 1

Questo esempio presuppone che tutti gli ID siano coerenti e sequenziali (in caso contrario, un ulteriore ROW_NUMBER() colonna o una nuova colonna identità dovrebbe essere utilizzata per garantire operazioni di resto corrette sull'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

Esempio n. 2:nessuna dipendenza dagli ID sequenziali

Questo è un esempio più pratico perché i valori ID effettivi non contano, solo la sequenza di righe.

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;

Esempio n. 3:cursore

Ancora una volta, sarebbe meglio evitare di eseguire una query come questa in tempo reale e utilizzare un processo ETL pianificato e transazionale. In base alla mia esperienza, dati semistrutturati come questo sono soggetti ad anomalie.

Mentre gli esempi #1 e #2 (e le soluzioni fornite da altri) dimostrano modi intelligenti di lavorare con i dati, un modo più pratico per trasformare questi dati sarebbe un cursore. Come mai? potrebbe effettivamente funzionare meglio (nessuna query nidificata, ricorsione, rotazione o numerazione delle righe) e anche se è più lento offre opportunità molto migliori per la gestione degli errori.

-- 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;

Prestazioni

Ho creato dati di origine di esempio di 100.000 righe e i tre esempi sopra citati sembrano più o meno equivalenti per la trasformazione dei dati.

Ho creato un milione di righe di dati di origine e una query simile alla seguente offre prestazioni eccellenti per la selezione di un sottoinsieme di righe (come verrebbe utilizzato in una griglia su una pagina Web o in un report).

-- 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;

Vedo tempi di esecuzione di 4-10 ms (i7-3960x) rispetto a un set di un milione di record.