2011-02-07 5 views
0

Dans un programme que je conserve, le client nous a donné une instruction SQL massive (~ 500 lignes). Il est utilisé pour générer des fichiers plats avec des enregistrements de longueur fixe pour la transmission de données à une autre grande entreprise. Comme c'est un fichier plat massif, il n'est pas relationnel et les formes normales de données sont réduites. Donc, si vous avez un enregistrement qui peut avoir plusieurs codes associés, dans ce cas jusqu'à 19, ils ont tous été écrits en une seule ligne, mais séparés, dans le fichier plat.Réduire plusieurs enregistrements dans un seul enregistrement avec plusieurs colonnes

Remarque: cet exemple est simplifié.

Les données pourraient ressembler à ceci, avec trois tables:

RECORDS 
record_id firstname lastname  
--------------------------------  
123   Bob   Schmidt 
324   George  Washington 
325   Ronald  Reagan 
290   George  Clooney 


CODE_TABLE 
code_id  code_cd  code_txt 
-------------------------------- 
5   3   President 
2   4   Actor  
3   7   Plumber 


CODES_FOR_RECORDS 
record_id code_cd 
------------------- 
123   7  
325   3 
290   4 
324   3 
325   4 
123   4 

Cela doit produire des documents tels que:

firstname lastname code1  code2  code3 
Bob   Schmidt  Actor  Plumber  NULL 
George  Washington President NULL  NULL 
Ronald  Reagon  Actor  President NULL 
George  Clooney  Actor  NULL  NULL 

La partie de la requête en cours nous a donné ressemble à ceci, mais avec 19 colonnes de code au lieu de la 5:

select 
    x.record_id, 
    max(case when x.rankk = 1 then code_txt end) as CodeColumn1, 
    max(case when x.rankk = 2 then code_txt end) as CodeColumn2, 
    max(case when x.rankk = 3 then code_txt end) as CodeColumn3, 
    max(case when x.rankk = 4 then code_txt end) as CodeColumn4, 
    max(case when x.rankk = 5 then code_txt end) as CodeColumn5, 
from 
    (
     select 
      r.record_id, 
      ct.code_txt as ctag , 
      dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk 
     from    
      records as r 
      codes_for_records as cfr, 
      code_table as ct 
     where 
      r.record_id = cfr.record_id 
      and ct.code_cd = cfr.code_cd 
      and cfr.code_cd is not null 
      and ct.code_txt not like '%V%' 
    ) as x 
where 
    x.record_id is not null 
group by 
    x.record_id 

Je trimm édité des choses à des fins de simplicités, mais la déclaration réelle comprend une requête interne et une jointure et plus où les conditions, mais cela devrait faire passer l'idée. Mon cerveau me dit qu'il doit y avoir un meilleur moyen, mais je ne suis pas un expert SQL. Nous utilisons DB2 v8 si cela peut vous aider. Et les codes doivent être dans des colonnes séparées, donc pas de fusionner les choses en une seule chaîne. Y a-t-il une solution plus propre que celle-ci?

Mise à jour:

J'ai fini juste refacorting la requête initiale, il utilise le seuil de vilaine affaire MAX(), mais dans l'ensemble de la requête est beaucoup plus facile à lire en raison de retravailler d'autres parties.

+0

Lorsque j'écris du code pour transformer des données manuellement, la procédure est rarement inférieure à 1 000 lignes. Ce code me semble assez simple et direct. – HLGEM

+0

Peut-être que c'est que je ne suis pas vraiment familier avec SQL, parce que pour moi cela semble un peu compliqué. Et une partie de cela est de 1000 lignes lorsqu'il est séparé en morceaux logiques n'est pas mauvais. 500 lignes de SQL qui est tellement interconnecté il semble que spaghetti est une autre chose, à mon humble avis. – troutinator

Répondre

0

Il semble que ce que vous cherchez est pivoting.

WITH joined_table(firstname, lastname, code_txt, rankk) AS 
(
SELECT 
    r.firstname, 
    r.lastname, 
    ct.code_txt, 
    dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk 
FROM 
    records r 
INNER JOIN 
    codes_for_records cfr 
    ON r.record_id = cfr.record_id 
INNER JOIN 
    codes_table ct 
    ON ct.code_cd = cfr.code_cd 
), 

decoded_table(firstname, lastname, 
    CodeColumn1, CodeColumn2, CodeColumn3, CodeColumn4, CodeColumn5) AS 
(
    SELECT 
    firstname, 
    lastname, 
    DECODE(rankk, 1, code_txt), 
    DECODE(rankk, 2, code_txt), 
    DECODE(rankk, 3, code_txt), 
    DECODE(rankk, 4, code_txt), 
    DECODE(rankk, 5, code_txt) 
    FROM 
    joined_table jt 
) 

SELECT 
    firstname, 
    lastname, 
    MAX(CodeColumn1), 
    MAX(CodeColumn2), 
    MAX(CodeColumn3), 
    MAX(CodeColumn4), 
    MAX(CodeColumn5) 
FROM 
    decoded_table dt 
GROUP BY 
    firstname, 
    lastname; 

Notez que je ne l'ai jamais fait moi-même auparavant. Je compte sur le linked document comme référence.

Vous devrez peut-être inclure le paramètre record_id pour prendre en compte les noms en double.

Modifier: Ajout du GROUP BY.

+0

Intéressant. Je vais donner un coup de feu. – troutinator

+0

Je pense toujours que toutes ces affaires 'MAX()' sont moche. – troutinator

0

L'une des solutions possibles est l'utilisation de la requête récursive:

with recursive_view (record_id, rankk, final) as 
(
    select 
    record_id, 
    rankk, 
    cast (ctag as varchar (100)) 
    from inner_query t1 

    union all 

    select 
    t1.record_id, 
    t1.rankk, 
    /* all formatting here */ 
    cast (t2.final || ',' || t1.ctag as varchar (100)) 
    from 
    inner_query t1, 
    recursive_view t2 
    where 
    t2.rankk < t1.rankk 
    and t1.record_id = t2.record_id 
    and locate(t1.ctag, t2.final) = 0 
) 
select record_id, final from recursive_view; 

ne peut garantir que cela fonctionne, mais nous espérons qu'il sera utile. Une autre méthode consiste à utiliser la fonction d'agrégat personnalisée.

+0

Merci pour les suggestions. Cependant, chaque code doit être laissé sur sa colonne. Il ne peut pas être joint en une chaîne géante. – troutinator

+0

Il peut être formaté par remplissage pour ressembler à un groupe de colonnes. Sinon, sans pivot et SQL dynamique, vous êtes obligé d'utiliser des constructions max (cas) dupliquées. –

Questions connexes