Procedimiento almacenado para devolver datos de tabla creados dinámicamente


10

Breve historia de fondo, estamos trabajando con un proveedor externo que tiene un sistema de encuestas. El sistema no está necesariamente diseñado mejor cuando crea una nueva encuesta y crea una nueva tabla, es decir:

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)

Las tablas se generan con el SurveyIdal final del nombre ( Library_) y las columnas de Preguntas se generan con el QuestionIdal final ( Q_). Para aclarar, las preguntas se almacenan en una tabla separada, por lo que si bien los identificadores de preguntas son secuenciales, no comienzan en 1 para cada encuesta. Las columnas de preguntas se basarán en la identificación asignada en la tabla.

Parece bastante simple para consultar, excepto que necesitamos extraer los datos de todas las tablas de encuestas para enviarlas a otro sistema y aquí es donde entra el problema. Dado que las tablas se crean automáticamente cuando el frente agrega una nueva encuesta Al finalizar la aplicación, el otro sistema no puede manejar este tipo de estructura. Necesitan que los datos sean consistentes para que puedan consumir.

Así que me encargaron escribir un procedimiento almacenado que extraiga los datos de todas las tablas de la Encuesta y lo coloque en el siguiente formato:

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

Al tener los datos de todas las tablas en el mismo formato, cualquiera puede consumirlos sin importar cuántas tablas de encuesta y preguntas existan.

Escribí un procedimiento almacenado que parece estar funcionando, pero me pregunto si me falta algo o si hay una mejor manera de manejar este tipo de situación.

Mi código:

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)

He creado un SQL Fiddle con algunos datos de muestra y el código.

¿Hay alguna forma diferente de escribir este tipo de consulta? ¿Hay algún problema notable con él?

Desafortunadamente, hay muchas incógnitas con esto ... cuántas tablas tendremos y cuántas preguntas por encuesta. Yo diría que tendremos entre 25 y 50 encuestas, con 2-5 preguntas cada una.


1
Tengo miedo de preguntar, pero, "¿Cuántas mesas?"
RBarryYoung

@RBarryYoung En este punto, eso es desconocido porque dependerá de cuántas encuestas se creen. Eso es parte del problema.
Taryn

Danos un rango entonces. Importa mucho.
RBarryYoung

Yo diría que entre 25 y 50 mesas.
Taryn

Respuestas:


2

Basado en los comentarios de personas en el chat, decidí cambiar mi script ligeramente a INSERT INTOuna tabla temporal en lugar de crear una larga instrucción SQL para ejecutar al final. Entonces, al final, mi procedimiento almacenado contiene lo siguiente:

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

Ver SQL Fiddle con el script final

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.