Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Oracle SQL-Paare von Rechts-Links-Sequenznummern mit Bezeichnern

Hier ist eine Lösung, die allgemeiner funktioniert, auch wenn die Paare nicht unbedingt direkt nebeneinander liegen. (Falls dies tatsächlich ERFORDERLICH ist, wenn Teile nicht gepaart werden können, wenn ihre IDs nicht aufeinander folgen, kann diese Bedingung zur Abfrage hinzugefügt werden.)

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
;

Ausgabe :

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 

Erläuterung:In der inneren Abfrage füge ich den Anfangsdaten zwei weitere Spalten hinzu. Eins, rn , zählt separat (von 1 beginnend und um 1 aufsteigend) für jeden Bezeichner, separat für 'L' und für 'R'. Dies wird verwendet, um die Paare zu bilden. Und ct gibt die kleinste der Gesamtzahlen für 'L' und 'R' für jeden Bezeichner an. In der äußeren Abfrage filtere ich einfach alle Zeilen heraus, in denen rn > ct ist - das sind die Zeilen ohne Paar in der Anfangstabelle. Was übrig bleibt, sind die Paare.

Hinzugefügt :Mit der zusätzlichen Bedingung, dass aus "aufeinanderfolgenden" Zeilen (gemessen an der id) ein Paar gebildet werden muss Spalte), wird dies eine interessantere Frage. Es ist ein Lücken-und-Inseln-Problem (identifiziere Gruppen aufeinanderfolgender Zeilen mit demselben Merkmal), aber mit einer Wendung:dem LR Der Wert muss innerhalb der Gruppe alternierend und nicht konstant sein. Die sehr effiziente "Tabibitosan"-Methode kann hier (glaube ich) nicht angewendet werden; Die allgemeinere Methode "Gruppenstart" funktioniert. Das habe ich hier verwendet. Beachten Sie, dass ich am Ende die allerletzte Zeile in einer Gruppe auslasse, wenn die Zählung für die Gruppe eine ungerade Zahl ist. (Wir können zwei oder vier oder sechs aufeinanderfolgende Reihen finden, die ein oder zwei oder drei Paare bilden, aber keine ungerade Anzahl von Reihen mit abwechselndem LR). Beachten Sie auch, dass, wenn zwei Zeilen denselben Bezeichner AND LR haben, die zweite Zeile immer eine NEUE Gruppe beginnt. Wenn sie also tatsächlich Teil eines Paares ist (mit der Zeile NACHHER), wird dies von dieser Lösung korrekt erfasst.

Vergleichen Sie dies mit der MATCH_RECOGNIZE-Lösung für Oracle 12 und höher, die ich separat gepostet habe – und schätzen Sie, wie viel einfacher sie ist!

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
;