sql >> Base de Datos >  >> RDS >> Oracle

Oracle SQL empareja números secuenciales de derecha a izquierda con identificadores

Aquí hay una solución que funciona de manera más general, incluso si los pares no se encuentran necesariamente uno al lado del otro. (Si eso es REQUERIDO, si las partes no se pueden emparejar si sus ID no son consecutivas, esa condición se puede agregar a la consulta).

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
;

Salida :

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 

Explicación:en la consulta interna, agrego dos columnas más a los datos iniciales. Uno, rn , cuenta por separado (empezando por 1 y aumentando de 1 en 1) para cada identificador, por separado para 'L' y para 'R'. Esto se utilizará para formar las parejas. Y, ct da el menor de los recuentos totales de 'L' y 'R' para cada identificador. En la consulta externa, solo filtro todas las filas donde rn > ct - esas son las filas sin par en la tabla inicial. Lo que queda son las parejas.

AÑADIDO :con la condición adicional de que se debe formar un par a partir de filas "consecutivas" (medidas por el id columna), esto se convierte en una pregunta más interesante. Es un problema de lagunas e islas (identificar grupos de filas consecutivas con la misma característica), pero con un giro:el LR el valor debe alternarse dentro del grupo, en lugar de ser constante. El muy eficiente método "tabibitosan" no se puede aplicar aquí (creo); el método de "inicio de grupo", que es más general, funciona. Esto es lo que usé aquí. Tenga en cuenta que al final dejo fuera la última fila de un grupo, si el conteo del grupo es un número impar. (Podemos encontrar dos, o cuatro, o seis filas consecutivas que forman uno o dos o tres pares, pero no un número impar de filas con LR alternas). Tenga en cuenta también que si dos filas tienen el mismo identificador Y LR, la segunda fila siempre comenzará un grupo NUEVO, por lo que si de hecho es parte de un par (con la fila DESPUÉS), esta solución lo detectará correctamente.

Compare esto con la solución MATCH_RECOGNIZE para Oracle 12 y superior que publiqué por separado, ¡y aprecie cuánto más simple es!

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
;