Faire pivoter les lignes dans plusieurs colonnes


21

J'ai une instance SQL Server qui a un serveur lié à un serveur Oracle. Il existe une table sur le serveur Oracle appelée PersonOptionsqui contient les données suivantes:

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

J'ai besoin de faire pivoter ces données pour que les résultats soient:

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

Aucune suggestion?

Réponses:


20

Vous pouvez effectuer cette transformation de données de plusieurs manières. Vous avez accès à la PIVOTfonction, ce sera la plus simple, mais sinon, vous pouvez utiliser une fonction d'agrégation et a CASE.

Version agrégée / cas:

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;

Voir SQL Fiddle avec démo

Pivot statique:

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

Voir SQL Fiddle avec démo

Version dynamique:

Les deux versions ci-dessus fonctionnent très bien si vous avez un nombre connu de valeurs, mais si vos valeurs sont inconnues, alors vous voudrez implémenter SQL dynamique et dans Oracle, vous pouvez utiliser une procédure:

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;
/

Ensuite, vous retournez les résultats, vous utiliserez:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

Les résultats sont les mêmes avec toutes les versions:

| PERSONID | OPTIONA | OPTIONB | OPTIONC |
------------------------------------------
|        1 |       1 |       1 |       0 |
|        2 |       0 |       0 |       1 |
|        3 |       0 |       1 |       0 |
|        4 |       1 |       0 |       1 |

Cependant, la solution Static Pivot suppose qu'il n'y a que trois options. Et si vous avez un nombre d'options potentiellement illimité? ABCDEFGHIJK par exemple? N'y a-t-il pas un moyen de rendre le pivot dynamique avec SQL standard? Au lieu de faire des options les en-têtes de colonne, pourrions-nous simplement les mettre dans les colonnes? Donc, cela ressemblerait à ceci: | PERSONID | Colonne2 | Colonne3 | Colonne4 | ------------------------------------------ | 1 | A | B | null | | 2 | C | null | null | | 3 | null | C | null |
Matthew

1
@Matthew, vous devez utiliser Dynamic Sql comme je le démontre dans la dernière partie de la réponse.
Taryn

Merci pour la réponse rapide! Je fais cela en créant une nouvelle colonne et en remplissant toutes les options séparées par des virgules. Le col génère à partir d'une sous-requête sélectionnant dans les mêmes tables where a.personId = a2.personId order by a2.personId for xml path(''). a2 est la table de la sous-requête. Ensuite, je sépare les données dans Excel en utilisant du texte dans des colonnes avec une virgule comme délimiteur. J'espérais trouver un moyen de le faire dans SQL standard sans avoir à écrire une procédure, mais peut-être qu'il n'y a aucun moyen. Je dois courir pour le moment mais je vais essayer de poster un exemple pour mieux l'expliquer.
Matthew

9

Ce serait l'équivalent dans la syntaxe SQL Server. Sur la base de ma lecture des documents Oracle, NULLIF et PIVOT semblent avoir le même format que leur parent SQL Server. Le défi sera la liste croisée dynamique qui doit être statique à moins que vous ne rendiez la requête dynamique comme le montre Itzik mais je n'ai aucune idée si cela peut être traduit en 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;

5

Je préfère faire pivoter la requête manuellement, mais vous pouvez également l'utiliser 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

1
N'hésitez pas à expliquer celui-ci un peu plus. Qu'est-ce que le pivot offre aux autres? Et quand ça tombe en panne? N'oubliez pas que vous répondez pour la postérité, pas pour quelqu'un ayant une expertise de domaine spécifique dans les choses que vous savez également.
jcolebrand

2
@jcolebrand: Il s'agit davantage de préférences personnelles - je pense moi-même que la PIVOTsyntaxe est plus compliquée que l'approche que j'utilise. Cependant, je suis conscient que les deux donnent le même résultat, et je suis d'accord que d'autres personnes peuvent penser le contraire.
a1ex07

1
Astuce: Utilisez le bouton Modifier ;-) ~ Nous aimons encourager plus qu'une réponse de code-réponse.
jcolebrand
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.