¿Cómo aplanar los resultados de una tabla con dos tablas relacionadas "muchas"?


9

He reorganizado algunas tablas en mi base de datos para que sean más flexibles, pero no estoy realmente seguro de cómo escribir el SQL para extraer datos significativos de ellas.

Tengo las siguientes tablas (algo abreviado para un ejemplo más claro):

CREATE TABLE Loans(
    Id int,
    SchemaId int,
    LoanNumber nvarchar(100)
);

CREATE TABLE SchemaFields(
    Id int,
    SchemaId int,
    FieldName nvarchar(255)
);

CREATE TABLE LoanFields(
    Id int,
    LoanId int,
    SchemaFieldId int,
    FieldValue nvarchar(4000)
);

Con los siguientes datos:

INSERT INTO Loans (Id, SchemaId, LoanNumber) VALUES (1, 1, 'ABC123');

INSERT INTO SchemaFields (Id, SchemaId, FieldName) VALUES (1, 1, 'First Name');
INSERT INTO SchemaFields (Id, SchemaId, FieldName) VALUES (2, 1, 'Last Name');

INSERT INTO LoanFields (Id, LoanId, SchemaFieldId, FieldValue) VALUES (1, 1, 1, 'John');
INSERT INTO LoanFields (Id, LoanId, SchemaFieldId, FieldValue) VALUES (2, 1, 2, 'Doe');

El objetivo es obtener una consulta plana para un préstamo con todos sus campos. (En el mundo real probablemente habrá entre 20-30 campos para el mismo esquema, pero solo tenemos 2 en el ejemplo):

LoanNumber   First Name    Last Name
----------   -----------   ----------
ABC123       John          Doe

No puedo usar un pivote que haga referencia al 'Nombre' y 'Apellido' porque no tendré idea de lo que realmente estará allí.

Tengo un SQL Fiddle aquí con un esquema ya en su lugar.

¿Cómo puedo obtener el resultado deseado?

Respuestas:


7

Esto se puede hacer usando la función PIVOT , pero dado que parece que desea cambiar la consulta basada en el schemaId, entonces querrá usar SQL dinámico.

Si tenía un número conocido de valores o conocía las columnas para un ID de esquema específico, podría codificar la consulta. Una consulta estática sería:

select loannumber,
  [First Name], 
  [Middle Name], 
  [Last Name]
from
(
  select 
    l.loannumber,
    sf.fieldname,
    lf.fieldvalue
  from loans l
  left join loanfields lf
    on l.id = lf.loanid
  left join schemafields sf
    on lf.schemafieldid = sf.id
    and l.schemaid = sf.schemaid
) src
pivot
(
  max(fieldvalue)
  for fieldname in ([First Name], [Middle Name], [Last Name])
)piv;

Ver SQL Fiddle con Demo .

Si tenía un número desconocido o desea que las columnas cambien en función de lo SchemaIdque está pasando a un procedimiento, utilizará SQL dinámico para generar la cadena SQL:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX),
    @schemaId int = 1

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(FieldName) 
                    from SchemaFields 
                    where schemaid = @schemaid
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT loannumber,' + @cols + ' 
            from 
            (
              select 
                l.loannumber,
                sf.fieldname,
                lf.fieldvalue
              from loans l
              left join loanfields lf
                on l.id = lf.loanid
              left join schemafields sf
                on lf.schemafieldid = sf.id
                and l.schemaid = sf.schemaid
              where sf.schemaid = '+cast(@schemaid as varchar(10))+'
            ) x
            pivot 
            (
                max(fieldvalue)
                for fieldname in (' + @cols + ')
            ) p '

execute(@query);

Ver SQL Fiddle con Demo . Ambas consultas generarán el resultado:

| LOANNUMBER | FIRST NAME | LAST NAME | MIDDLE NAME |
-----------------------------------------------------
|     ABC123 |       John |       Doe |      (null) |
|     XYZ789 |    Charles |     Smith |         Lee |

3

Puedes usar este patrón. Para obtener más campos de esquema, simplemente expanda en la 2da a la última línea con todos los nombres de campo de esquema.

select *
  from (
    select l.LoanNumber, s.FieldName, f.FieldValue
      from Loans l
      join Schemafields s on s.SchemaId = l.SchemaId
      join LoanFields f on f.LoanId = l.Id and f.SchemaFieldId = s.Id) p
pivot (
    max(FieldValue) for FieldName in ([First Name], [Last Name])
    ) v;

Si SchemaId = 1representa los campos para el Loantipo, entonces también puede generar dinámicamente la lista completa de schema field namesusar algo como lo siguiente.

declare @sql nvarchar(max) =
    stuff((select ',' + quotename(FieldName)
      from SchemaFields
     where SchemaId = 1
       for xml path(''), type).value('/','nvarchar(max)'),1,1,'');
select @sql = '
select *
  from (
    select l.LoanNumber, s.FieldName, f.FieldValue
      from Loans l
      join Schemafields s on s.SchemaId = l.SchemaId
      join LoanFields f on f.LoanId = l.Id and f.SchemaFieldId = s.Id) p
pivot (
    max(FieldValue) for FieldName in (' + @sql + ')
    ) v';
exec (@sql);
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.