Oracle
 sql >> Database >  >> RDS >> Oracle

Oracle SQL accoppia numeri sequenziali destra sinistra con identificatori

Ecco una soluzione che funziona più in generale, anche se le coppie non si trovano necessariamente una accanto all'altra. (Se in effetti è RICHIESTO, se le parti non possono essere accoppiate se i loro ID non sono consecutivi, tale condizione può essere aggiunta alla query.)

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
;

Risultato :

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 

Spiegazione:Nella query interna, aggiungo altre due colonne ai dati iniziali. Uno, rn , conta separatamente (partendo da 1 e incrementando di 1) per ogni identificatore, separatamente per 'L' e per 'R'. Questo sarà usato per formare le coppie. E, ct fornisce il minimo dei conteggi totali per "L" e "R" per ciascun identificatore. Nella query esterna, filtro tutte le righe in cui rn > ct - quelle sono le righe senza coppia nella tabella iniziale. Ciò che resta sono le coppie.

AGGIUNTO :Con la condizione aggiuntiva che una coppia deve essere formata da righe "consecutive" (misurate dal id colonna), questa diventa una domanda più interessante. È un problema di lacune e isole (identificare gruppi di righe consecutive con la stessa caratteristica), ma con una svolta:il LR il valore deve essere alternato all'interno del gruppo, anziché costante. Il metodo molto efficiente "tabibitosan" non può essere applicato qui (credo); il metodo "inizio gruppo", che è più generale, funziona. Questo è quello che ho usato qui. Nota che alla fine tralascio l'ultima riga in un gruppo, se il conteggio per il gruppo è un numero dispari. (Potremmo trovare due, quattro o sei righe consecutive che formano una o due o tre coppie, ma non un numero dispari di righe con LR alternato). Nota anche che se due righe hanno lo stesso identificatore AND LR, la seconda riga avvierà sempre un gruppo NEW, quindi se fa effettivamente parte di una coppia (con la riga DOPO), verrà catturato correttamente da questa soluzione.

Confrontalo con la soluzione MATCH_RECOGNIZE per Oracle 12 e versioni successive che ho pubblicato separatamente e apprezza quanto sia più semplice!

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
;