Хранимая процедура для возврата динамически созданных данных таблицы

10

Краткая история, мы работаем с внешним поставщиком, у которого есть система опросов. Система не обязательно разработана лучше всего в том случае, когда вы создаете новый опрос и система создает новую таблицу, а именно:

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)

Таблицы создаются SurveyIdв конце имени ( Library_), а столбцы с вопросом - QuestionIdв конце ( Q_). Для пояснения вопросы хранятся в отдельной таблице, поэтому, хотя идентификаторы вопросов являются последовательными, они не начинаются с 1 для каждого опроса. Столбцы вопросов будут основаны на идентификаторе, назначенном им в таблице.

Кажется достаточно простым для запроса, за исключением того, что нам нужно извлечь данные из всех таблиц опросов для отправки в другую систему, и именно здесь возникает проблема. Так как таблицы создаются автоматически, когда фронт-опрос добавляет новый опрос. конечное приложение, другая система не может обработать этот тип структуры. Им нужны данные, чтобы они были согласованными для потребления.

Поэтому мне было поручено написать хранимую процедуру, которая будет извлекать данные из всех таблиц Survey и размещать их в следующем формате:

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

Имея данные для всех таблиц в одном и том же формате, они могут быть использованы кем угодно, независимо от того, сколько существует таблиц опросов и вопросов.

Я написал хранимую процедуру, которая, кажется, работает, но мне интересно, если я что-то упустил или есть лучший способ справиться с такой ситуацией.

Мой код:

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)

Я создал SQL Fiddle с некоторыми примерами данных и кодом.

Есть ли другой способ написания запроса такого типа? Есть ли какие-либо заметные проблемы с этим?

К сожалению, есть много неизвестных с этим ... сколько у нас будет таблиц и сколько вопросов в опросе. Я бы сказал, что у нас будет от 25 до 50 опросов по 2-5 вопросов в каждом.

Тарын
источник
1
Боюсь спросить, но "Сколько столов?"
RBarryYoung
@RBarryYoung На данный момент, это неизвестно, потому что это будет зависеть от того, сколько опросов создано. Это часть проблемы.
Тарын
Дайте нам диапазон тогда. Это имеет большое значение.
RBarryYoung
Я бы сказал где-нибудь из 25-50 таблиц.
Тарын

Ответы:

2

Основываясь на комментариях людей в чате, я решил немного изменить свой сценарий на INSERT INTOвременную таблицу вместо создания одного длинного оператора SQL для выполнения в конце. В итоге моя хранимая процедура содержит следующее:

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

Смотрите SQL Fiddle с финальным скриптом

Тарын
источник