Oracle
 sql >> Base de données >  >> RDS >> Oracle

Oracle SQL Pair Right Left Sequential Numbers with Identifiers

Voici une solution qui fonctionne plus généralement, même si les paires ne se trouvent pas forcément juste à côté. (Si cela est en fait OBLIGATOIRE, si les pièces ne peuvent pas être appariées si leurs ID ne sont pas consécutifs, cette condition peut être ajoutée à la requête.)

with
     test_data ( id, lr, identifier ) as (
       select '001', 'L', 'B15A' from dual union all
       select '002', 'R', 'A15C' from dual union all
       select '003', 'L', 'A15C' from dual union all
       select '004', 'R', 'A15C' from dual union all
       select '005', 'L', 'A15C' from dual union all
       select '006', 'R', 'D5A2' from dual union all
       select '009', 'R', 'D5A2' from dual union all
       select '010', 'L', 'E5A6' from dual union all
       select '011', 'R', 'E5A6' from dual union all
       select '012', 'L', 'E5A6' from dual union all
       select '013', 'R', 'E5A6' from dual union all
       select '014', 'R', 'H9S5' from dual union all
       select '017', 'L', 'EE5A' from dual union all
       select '018', 'R', 'EE5A' from dual
     )
-- end of test data, the solution (SQL query) begins below this line
select id, lr, identifier
from ( select id, lr, identifier,
              row_number() over (partition by identifier, lr order by id) as rn,
              least( count(case when lr = 'L' then 1 end) over (partition by identifier),
                     count(case when lr = 'R' then 1 end) over (partition by identifier)
                   ) as least_count
       from   test_data
)
where rn <= least_count
order by id               --  ORDER BY is optional
;

Sortie :

ID  LR IDENTIFIER
--- -- ----------
002 R  A15C
003 L  A15C
004 R  A15C
005 L  A15C
010 L  E5A6
011 R  E5A6
012 L  E5A6
013 R  E5A6
017 L  EE5A
018 R  EE5A

 10 rows selected 

Explication :Dans la requête interne, j'ajoute deux colonnes supplémentaires aux données initiales. Un, rn , compte séparément (en partant de 1 et en incrémentant de 1) pour chaque identifiant, séparément pour 'L' et pour 'R'. Cela servira à former les binômes. Et, ct donne le plus petit des nombres totaux pour 'L' et 'R' pour chaque identifiant. Dans la requête externe, je filtre simplement toutes les lignes où rn > ct - ce sont les lignes sans paire dans le tableau initial. Ce qui reste, ce sont les paires.

AJOUTÉ  :Avec la condition supplémentaire qu'une paire doit être formée à partir de lignes "consécutives" (mesurées par le id colonne), cela devient une question plus intéressante. C'est un problème d'espaces et d'îlots (identifier des groupes de lignes consécutives avec la même caractéristique), mais avec une torsion :le LR la valeur doit être alternée au sein du groupe, plutôt que constante. La méthode "tabibitosan" très efficace ne peut pas être appliquée ici (je pense); la méthode "début de groupe", qui est plus générale, fonctionne. C'est ce que j'ai utilisé ici. Notez qu'à la fin, je laisse de côté la toute dernière ligne d'un groupe, si le nombre pour le groupe est un nombre impair. (On peut trouver deux, ou quatre, ou six rangées consécutives qui forment une ou deux ou trois paires, mais pas un nombre impair de rangées avec LR alterné). Notez également que si deux lignes ont le même identifiant ET LR, la deuxième ligne commencera toujours un nouveau groupe, donc si elle fait en fait partie d'une paire (avec la ligne APRÈS), cela sera correctement détecté par cette solution.

Comparez cela à la solution MATCH_RECOGNIZE pour Oracle 12 et supérieur que j'ai publiée séparément - et appréciez à quel point c'est plus simple !

with
     prep ( id, lr, identifier, flag ) as (
       select id, lr, identifier,
              case when identifier = lag(identifier) over (order by id) 
                    and lr        != lag(lr)         over (order by id)
                   then null else 1 end
       from test_data    --  replace "test_data" with actual table name
     ), 
     with_groups ( id, lr, identifier, gp ) as (
       select id, lr, identifier,
              sum(flag) over (order by id)
       from   prep
     ),
     with_rn ( id, lr, identifier, rn, ct ) as (
       select id, lr, identifier,
              row_number() over (partition by identifier, gp order by id),
              count(*)     over (partition by identifier, gp)
       from   with_groups
     )
select   id, lr, identifier
from     with_rn
where    rn < ct or mod(rn, 2) = 0
order by id               --  ORDER BY is optional
;