Pivote las filas en varias columnas.

21

Tengo una instancia de SQL Server que tiene un servidor vinculado a un servidor Oracle. Hay una tabla en el servidor de Oracle llamada PersonOptionsque contiene los siguientes datos:

╔══════════╦══════════╗
║ PersonID ║ OptionID ║
╠══════════╬══════════╣
║        1 ║ A        ║
║        1 ║ B        ║
║        2 ║ C        ║
║        3 ║ B        ║
║        4 ║ A        ║
║        4 ║ C        ║
╚══════════╩══════════╝

Necesito pivotar esos datos para que los resultados sean:

╔══════════╦═════════╦══════════╦══════════╗
║ PersonID ║ OptionA ║ Option B ║ Option C ║
╠══════════╬═════════╬══════════╬══════════╣
║        1 ║       1 ║        1 ║          ║
║        2 ║         ║          ║        1 ║
║        3 ║         ║        1 ║          ║
║        4 ║       1 ║          ║        1 ║
╚══════════╩═════════╩══════════╩══════════╝

¿Alguna sugerencia?

Yo no
fuente

Respuestas:

20

Hay algunas formas en que puede realizar esta transformación de datos. Tiene acceso a la PIVOTfunción, entonces será la más fácil, pero si no, puede usar una función agregada y a CASE.

Agregado / Versión del caso:

select personid,
  max(case when optionid = 'A' then 1 else 0 end) OptionA,
  max(case when optionid = 'B' then 1 else 0 end) OptionB,
  max(case when optionid = 'C' then 1 else 0 end) OptionC
from PersonOptions
group by personid
order by personid;

Ver SQL Fiddle con Demo

Pivote Estático:

select *
from
(
  select personid, optionid
  from PersonOptions
) src
pivot
(
  count(optionid)
  for optionid in ('A' as OptionA, 'B' OptionB, 'C' OptionC)
) piv
order by personid

Ver SQL Fiddle con Demo

Versión Dinámica:

Las dos versiones anteriores funcionan muy bien si tiene un número conocido de valores, pero si sus valores son desconocidos, entonces querrá implementar sql dinámico y en Oracle puede usar un procedimiento:

CREATE OR REPLACE procedure dynamic_pivot_po(p_cursor in out sys_refcursor)
as
    sql_query varchar2(1000) := 'select personid ';

    begin
        for x in (select distinct OptionID from PersonOptions order by 1)
        loop
            sql_query := sql_query ||
                ' , min(case when OptionID = '''||x.OptionID||''' then 1 else null end) as Option_'||x.OptionID;

                dbms_output.put_line(sql_query);
        end loop;

        sql_query := sql_query || ' from PersonOptions group by personid order by personid';
        dbms_output.put_line(sql_query);

        open p_cursor for sql_query;
    end;
/

Luego devuelve los resultados, usará:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

Los resultados son los mismos con todas las versiones:

| PERSONID | OPTIONA | OPTIONB | OPTIONC |
------------------------------------------
|        1 |       1 |       1 |       0 |
|        2 |       0 |       0 |       1 |
|        3 |       0 |       1 |       0 |
|        4 |       1 |       0 |       1 |
Taryn
fuente
Sin embargo, la solución de pivote estático supone que solo hay tres opciones. ¿Qué pasa si tiene un número potencialmente ilimitado de opciones? ABCDEFGHIJK por ejemplo? ¿No hay una manera de hacer que el pivote sea dinámico con sql regular? En lugar de hacer que las opciones sean encabezados de columna, ¿podríamos ponerlas en las columnas? Entonces se vería así: | PERSONA | Columna2 | Columna3 | Columna4 | ------------------------------------------ | 1 | A | B | nulo | El | 2 | C | nulo | nulo | El | 3 | nulo | C | nulo |
Mateo
1
@Matthew tendrías que usar Dynamic Sql como lo demuestro en la última parte de la respuesta.
Taryn
¡Gracias por la rápida respuesta! Realmente hago esto creando una nueva columna y rellenando todas las opciones allí separadas por comas. El col genera a partir de una selección sub consulta de las mismas tablas where a.personId = a2.personId order by a2.personId for xml path(''). a2 es la tabla en la subconsulta. Luego separo los datos en Excel usando texto en columnas con coma como delimitador. Esperaba encontrar una manera de hacer esto en sql regular sin tener que escribir un procedimiento, pero tal vez no haya manera. Tengo que correr en este momento, pero intentaré publicar un ejemplo para explicarlo mejor.
Mateo
9

Esto sería el equivalente en la sintaxis de SQL Server. Según mi lectura de los documentos de Oracle, NULLIF y PIVOT parecen tener el mismo formato que sus parientes de SQL Server. El desafío será la lista dinámica que debe ser estática a menos que haga que la consulta sea dinámica como lo demuestra Itzik, pero no tengo idea si eso se puede traducir a P / SQL

WITH PersonOptions(PersonID, OptionId) AS
(
    SELECT 1, 'A'
    UNION ALL SELECT 1, 'B'
    UNION ALL SELECT 2, 'C'
    UNION ALL SELECT 3, 'B'
    UNION ALL SELECT 4, 'A'
    UNION ALL SELECT 4, 'C'
)
SELECT
    P.PersonId
,   NULLIF(P.A, 0) AS OptionA
,   NULLIF(P.B, 0) AS OptionB
,   NULLIF(P.C, 0) AS OptionC
FROM
    PersonOptions  PO
    PIVOT 
    (
        COUNT(PO.OptionId)
        FOR OPtionId IN (A, B, C)
    )  P;
billinkc
fuente
5

Prefiero realizar consultas dinámicas manualmente, pero también puede usarlas PIVOT.

SELECT PersonID,
MAX(CASE WHEN OptionId ='A' THEN 1 END) AS OptionA,
MAX(CASE WHEN OptionId ='B' THEN 1 END) AS OptionB, 
MAX(CASE WHEN OptionId ='C' THEN 1 END) AS OptionC
FROM PersonOptions
GROUP BY PersonID
a1ex07
fuente
1
Siéntase libre de explicar esto solo un poco más. ¿Qué proporciona el pivote que los demás no pueden? ¿Y cuándo se rompe eso? Recuerde que está respondiendo por la posteridad, no por alguien con experiencia específica en el dominio en las mismas cosas que usted también conoce.
jcolebrand
2
@jcolebrand: se trata más de preferencias personales: creo que la PIVOTsintaxis es más complicada en comparación con el enfoque que uso. Sin embargo, soy consciente de que ambos dan el mismo resultado, y estoy de acuerdo en que otras personas pueden pensar lo contrario.
a1ex07
1
Sugerencia: utilice el botón de edición ;-) ~ Nos gusta alentar más que una respuesta de código de respuesta.
jcolebrand