2010-07-28 3 views
3

Je travaille sur un problème dans Oracle que j'ai du mal à résoudre «élégamment».SQL combine plusieurs identifiants pour créer un ID de groupe pour les enregistrements en double

I ont un extrait de données avec trois identificateurs différents: A, B, C

Chaque identificateur peut apparaître dans plus d'une rangée et chaque rangée peut avoir une ou plusieurs de ces trois identificateurs (à savoir la colonne est peuplé ou nul). Je veux être en mesure de regrouper tous les enregistrements ayant une combinaison de A, B ou C en commun et leur attribuer le même ID de groupe.

table Extrait montrant ce que les groupes éventuels doivent être:

Rownum | A | B | C | End group 
1  p  NULL NULL 1 
2  p  r  NULL 1 
3  q  NULL NULL 2 
4  NULL r  NULL 1 
5  NULL NULL s  2 
6  q  NULL s  2 

Mon approche originale était d'attribuer un Guid à chaque ligne dans l'extrait et créer une table de consultation pour les trois identifiants:

GUID | IDENTIFIER | IDENTIFIER TYPE | GROUP | END GROUP 
1  p   A     1  1 
2  p   A     1  1 
2  r   B     2  1 
3  q   A     3  3 
4  r   B     2  1 
5  s   C     4  3 
6  q   A     3  3 
6  s   C     4  3 

Puis regroupez par identifiant et attribuez un numéro de groupe. Les groupes, cependant, doivent être combinés si possible pour fournir la vue montrée dans le groupe final.

La seule solution que je peux penser à ce problème est d'utiliser des boucles, que je préfère éviter.

Toutes les idées seraient grandement appréciées.

Niall

+0

l'ID du groupe peut-il être simplement une concaténation de A, B et C? –

+0

Malheureusement pas. Si deux lignes ont l'un des identifiants en commun mais pas l'autre, la concaténation fournira deux identifiants de groupe différents.Par exemple: 2) A = null, B = null, C = q => q pq <> q mais les deux enregistrements doivent être dans le même groupe. – niallsco

+0

Pouvez-vous fournir votre structure? est ce tableau (a, b, c) ou tableau (Id, IdentifierType, Identifier) ​​ ? Dépend de la requête de structure sera différente –

Répondre

4

C'est vraiment un problème intéressant. Pourtant, je pense qu'il nous manque une définition d'un «groupe». Etant donné que dans votre exemple (p,null,null)(ligne1) et (null,r,null)(row4) parts pas d'identificateur commun et appartiennent au même groupe, je vais avec cette définition pour le groupement:

Une ligne appartient à un groupe si il partage au moins un identifiant avec au moins une ligne de ce groupe.

Cela signifie que nous pouvons "chaîner" des lignes. Cela conduit naturellement à une solution hiérarchique:

SQL> SELECT ID, a, b, c, MIN(grp) grp 
    2 FROM (SELECT connect_by_root(id) ID, 
    3     connect_by_root(a) a, 
    4     connect_by_root(b) b, 
    5     connect_by_root(c) c, 
    6     ID grp 
    7    FROM a 
    8   CONNECT BY NOCYCLE(PRIOR a = a 
    9       OR PRIOR b = b 
10       OR PRIOR c = c)) 
11 GROUP BY ID, a, b, c 
12 ORDER BY ID; 

     ID A   B   C     GRP 
---------- ---------- ---------- ---------- ---------- 
     1 p           1 
     2 p   r        1 
     3 q           3 
     4   r        1 
     5      s     3 
     6 q      s     3 

6 rows selected 

Vous pouvez exécuter le sous-requête pour comprendre la construction:

SQL> SELECT connect_by_root(id) ID, 
    2   connect_by_root(a) a, 
    3   connect_by_root(b) b, 
    4   connect_by_root(c) c, 
    5   substr(sys_connect_by_path(ID, '->'), 3) path, 
    6   ID grp 
    7 FROM a 
    8 CONNECT BY NOCYCLE(a = PRIOR a 
    9     OR b = PRIOR b 
10     OR c = PRIOR c); 

     ID A   B   C   PATH   GRP 
---------- ---------- ---------- ---------- -------- ---------- 
     1 p        1     1 
     1 p        1->2    2 
     1 p        1->2->4   4 
     2 p   r      2     2 
     2 p   r      2->1    1 
     2 p   r      2->4    4 
     3 q        3     3 
     3 q        3->6    6 
     3 q        3->6->5   5 
     4   r      4     4 
     4   r      4->2    2 
     4   r      4->2->1   1 
     5      s   5     5 
     5      s   5->6    6 
     5      s   5->6->3   3 
     6 q      s   6     6 
     6 q      s   6->3    3 
     6 q      s   6->5    5 

18 rows selected 
+0

+1. Solution extrêmement élégante, j'ai essayé d'utiliser "connecter par", mais sans chance. –

+0

+++ très bien fait. –

2

Utiliser fusion au lieu de la boucle:

Table a(a,b,c,groupId) 

Déclaration:

merge into a 
    USING (SELECT RANK() OVER(ORDER BY a,b,c) g, ROWID rid FROM a) SOURCE 
    ON (a.ROWID = SOURCE.rid) 
    WHEN MATCHED THEN UPDATE SET a.GroupId = SOURCE.g 

Il est le même que:

BEGIN 
     FOR x IN (SELECT RANK() OVER(ORDER BY a,b,c) g, ROWID rid FROM a) 
     LOOP 
      UPDATE a 
       SET GroupId = x.g 
      WHERE a.RowId = x.rid; 
     END LOOP; 
    END; 
+0

Aussi, je choisirais d'utiliser DENSE_RANK plutôt que RANK pour ce scénario. – APC

+0

@APC, pas besoin d'utiliser NVL - son inutile, le regroupement se fera de la même manière. –

+0

L'utilisation de cette solution, avec ou sans ces modifications, semble produire un group_id unique pour chaque ligne, ce qui signifie évidemment que les enregistrements ne sont pas groupés. Est-ce que j'ai râté quelque chose? Je suis un n00b concernant la commande de fusion. – niallsco

Questions connexes