it-swarm-eu.dev

Uložená procedura pro vrácení dynamicky vytvořených dat tabulky

Rychlý příběh, pracujeme s externím prodejcem, který má průzkumový systém. Systém není nutně navržen jako nejlepší v tom, že když vytvoříte nový průzkum a systém vytvoří novou tabulku, tj .:

Tables
____
Library_1 -- table for Survey 1
SurveyId int
InstanceId int
Q_1 varchar(50)

Library_2 -- table for Survey 2
SurveyId int
InstanceId int
Q_2 int
Q_3 int
Q_4 varchar(255)

Tabulky jsou generovány s SurveyId na konci názvu (Library_) a sloupce otázek jsou generovány s QuestionId na konci (Q_). Pro objasnění jsou otázky uloženy v samostatné tabulce, takže zatímco čísla otázek jsou sekvenční, nezačíná u každého průzkumu 1. Sloupce otázek budou založeny na ID, které je jim přiřazeno v tabulce.

Vypadá to dost jednoduché na dotaz, s výjimkou, že musíme extrahovat data ze všech tabulek průzkumu, které mají být odeslány do jiného systému, a to je místo, kde se problém vyskytuje. Protože tabulky jsou vytvářeny automaticky, když je nový průzkum přidán front- na konci aplikace nemůže druhý systém zvládnout tento typ struktury. Potřebují, aby údaje byly konzistentní, aby je mohly spotřebovat.

Úkolem jsem tedy byl napsat uloženou proceduru, která vyjme data ze všech průzkumových tabulek a umístí je v následujícím formátu:

SurveyId    InstanceId    QNumber    Response
________    __________    _______    ________
1           1             1          great
1           2             1          the best
2           9             2          10
3           50            50         test

Tím, že mají data pro všechny tabulky ve stejném formátu, pak je může kdokoli spotřebovat bez ohledu na to, kolik průzkumných tabulek a otázek existuje.

Napsal jsem uloženou proceduru, která se zdá být funkční, ale zajímalo by mě, jestli mi něco chybí nebo jestli existuje lepší způsob, jak zvládnout tento typ situace.

Můj kód:

declare @sql varchar(max) = ''
declare @RowCount int = 1
declare @TotalRecords int = (SELECT COUNT(*) FROM SurveyData)

Declare @TableName varchar(50) = ''
Declare @ColumnName varchar(50) = ''

WHILE @RowCount <= @TotalRecords
    BEGIN

        SELECT @TableName = tableName, @ColumnName = columnName
        FROM SurveyData
        WHERE @RowCount = rownum


        SET @sql = @sql + 
            ' SELECT s.SurveyId
                , s.InstanceId
                , CASE WHEN columnName = ''' +  @ColumnName + ''' THEN REPLACE(columnName, ''Q_'', '''') ELSE '''' END as QuestionNumber
                , Cast(s.' + @ColumnName + ' as varchar(1000)) as ''Response''
            FROM SurveyData t 
            INNER JOIN ' + @TableName + ' s' +
                ' ON REPLACE(t.tableName, ''Library_'', '''') = s.SurveyID ' +
            ' WHERE t.columnName = ''' + @ColumnName + ''''

        IF @RowCount != @TotalRecords
            BEGIN
                set @sql = @sql + ' UNION ALL'
            END

        SET @RowCount = @RowCount + 1       
    END


exec(@sql)

Vytvořil jsem SQL Fiddle s některými ukázkovými daty a kódem.

Existuje jiný způsob, jak tento typ dotazu napsat? Jsou s tím nějaké znatelné problémy?

Bohužel s tím existuje mnoho neznámých ... kolik tabulek budeme mít a kolik otázek na průzkum. Řekl bych, že budeme mít mezi 25–50 průzkumy, každý se 2-5 otázkami.

10
Taryn

Na základě komentářů lidí v chatu jsem se rozhodl skript mírně změnit na INSERT INTO dočasná tabulka namísto vytvoření jednoho dlouhého příkazu SQL, který se má provést na konci. Nakonec moje uložená procedura obsahuje následující:

create table #SurveyData
(
    tableName varchar(50),
    columnName varchar(50),
    columnId int,
    rownum int
)

create table #results
(
    SurveyId int,
    InstanceId int,
    QuestionNumber int,
    Response varchar(1000)
)

-- insert the survey table structures for use
insert into #SurveyData (tableName, columnName, columnId, rownum)
select tables1.name, cols1.name, column_id, ROW_NUMBER() over(order by tables1.name, column_id)
from sys.all_columns cols1
inner join 
(
    SELECT *
    FROM sys.all_objects
    WHERE type = 'U' 
    AND upper(name) like 'LIBRARY%' 
) Tables1
    ON cols1.object_id = tables1.object_id
WHERE cols1.name Like 'Q_%'
ORDER BY tables1.name, column_id;


declare @sql varchar(max) = '';
declare @RowCount int = 1;
declare @TotalRecords int = (SELECT COUNT(*) FROM #SurveyData);

Declare @TableName varchar(50) = '';
Declare @ColumnName varchar(50) = '';

WHILE @RowCount <= @TotalRecords
    BEGIN

        SELECT @TableName = tableName, @ColumnName = columnName
        FROM #SurveyData
        WHERE @RowCount = rownum

        SET @sql = 'INSERT INTO #results ' +
                    ' SELECT s.SurveyId
                        , s.InstanceId
                        , CASE WHEN columnName = ''' +  @ColumnName + ''' THEN REPLACE(columnName, ''Q_'', '''') ELSE '''' END as QuestionNumber
                        , Cast(s.' + @ColumnName + ' as varchar(1000)) as ''Response''
                    FROM #SurveyData t 
                    INNER JOIN ' + @TableName + ' s' +
                    ' ON REPLACE(t.tableName, ''Library_'', '''') = s.SurveyID ' +
                    ' WHERE t.columnName = ''' + @ColumnName + ''''

        exec(@sql)

        SET @RowCount = @RowCount + 1       
    END

    SELECT SurveyId, InstanceId, QuestionNumber, Response
    FROM #results

drop table #SurveyData
drop table #results

Viz SQL Fiddle s konečným skriptem

2
Taryn