2009-08-12 15 views
6

Necesito generar un informe a partir de una tabla con la estructura y los datos que se indican a continuación.Oracle SQL para la agrupación continua

Ticket de tabla tiene los datos que se detallan a continuación.

ID   Assigned_To 
100  raju 
101  raju 
102  raju 
103  anil 
104  anil 
105  sam 
106  raju 
107  raju 
108  anil 

El Oracle SELECT debe generar el informe a continuación

From_Id   To_Id Assigned_To 
100     102  raju 
103     104  anil 
105     105  sam 
106     107  raju 
108     108  anil 

por favor alguien puede ayudar con la construcción de una consulta ..?

Gracias de antemano, Mathew.

+0

Quizás usando min (ID) y max (ID) – Jonathan

+0

Ops, ya veo. Perdona mi comentario anterior ... – Jonathan

Respuesta

2

Bien, esto no es bonito, pero funciona. Y nadie más ha contribuido con algo más bonito todavía, así que tal vez esta sea la manera de hacerlo.

select min(from_id), to_id, assigned_to from 
(
select from_id, max(to_id) as to_id, assigned_to from 
(
select t1.id as from_id, t2.id as to_id, t1.assigned_to 
from  ticket t1 
inner join ticket t2 on t1.assigned_to = t2.assigned_to and t2.id >= t1.id 
where not exists 
    (
    select * from ticket t3 
    where t3.ID > t1.ID 
    and t3.ID < t2.ID 
    and t3.assigned_to != t1.assigned_to 
    ) 
) x 
group by from_id, assigned_to 
) y 
group by to_id, assigned_to 
; 

Estoy usando mysql; es posible que exista una bondad oráica que lo haga más agradable, ya que es posible que existan algunos cuadros cuadrados más elegantes. Pero al menos es un comienzo.

0

Creo que quiere uno de los Oracle Analytic functions. Estás de suerte si estás utilizando Oracle porque otros RDBMS no tienen esta funcionalidad. Le permiten escribir SQL que le permite consultar datos en relación con filas adyacentes, como el cálculo de promedios móviles. Yo no tengo una base de datos Oracle aquí para jugar, pero yo creo que sería algo como esto:

SELECT MIN(ID) AS From_Id, MAX(ID) AS To_Id, Assigned_To 
FROM Ticket 
PARTITION BY Assigned_To 
ORDER BY From_Id 
+1

Esto es sintácticamente incorrecto pero más importante también semánticamente. Cada asignador puede estar presente varias veces en el conjunto de resultados deseado, pero aquí no lo haría ... Traté de dar una solución en una respuesta separada. –

2

Usted puede hacer lo imposible tratando de lograr esto en SQL puro o se puede crear algo un poco más largo pero es mucho más fácil de entender y más eficiente en el rendimiento: use pipelined function

En esencia, la función aceptaría un cursor de ref que debería ser preordenado por el ID, y luego canalizaría las filas solo cuando un bloque contiguo de registros ha terminado.

CREATE TABLE assignment 
(
    a_id   NUMBER, 
    assigned_to VARCHAR2(4000) 
); 

CREATE OR REPLACE package PCK_CONTIGUOUS_GROUPBY as 

TYPE refcur_t IS REF CURSOR RETURN assignment%ROWTYPE; 

TYPE outrec_typ IS RECORD ( 
    from_id NUMBER, 
    to_id  NUMBER, 
    assigned_to VARCHAR2(4000)); 

    TYPE outrecset IS TABLE OF outrec_typ; 

FUNCTION f_cont_groupby(p refcur_t) 
     RETURN outrecset PIPELINED; 

end; 
/

CREATE OR REPLACE package body pck_contiguous_groupby as 

FUNCTION f_cont_groupby(p refcur_t) RETURN outrecset PIPELINED IS 

    out_rec    outrec_typ; 
    in_rec    p%ROWTYPE; 
    first_id   assignment.a_id%type; 
    last_id    assignment.a_id%type; 
    last_assigned_to assignment.assigned_to%type; 

    BEGIN 

    LOOP 
    FETCH p INTO in_rec; 
    EXIT WHEN p%NOTFOUND; 


     IF last_id IS NULL THEN 
     -- First record: don't pipe 
     first_id := in_rec.a_id; 

     ELSIF last_id = in_rec.a_id - 1 AND last_assigned_to = in_rec.assigned_to THEN 
     -- Contiguous block: don't pipe 
     NULL; 

     ELSE 
     -- Block not contiguous: pipe 
     out_rec.from_id := first_id; 
     out_rec.to_id := last_id; 
     out_rec.assigned_to := last_assigned_to; 

     PIPE ROW(out_rec); 

     first_id := in_rec.a_id; 
     END IF; 

    last_id := in_rec.a_id; 
    last_assigned_to := in_rec.assigned_to; 

    END LOOP; 
    CLOSE p; 

    -- Pipe remaining row 
    out_rec.from_id := first_id; 
    out_rec.to_id := last_id; 
    out_rec.assigned_to := last_assigned_to; 

    PIPE ROW(out_rec); 

    RETURN; 
END; 

END pck_contiguous_groupby; 
/

y después de probarlo, llenar la tabla y ejecute:

SELECT * FROM TABLE(pck_contiguous_groupby.f_cont_groupby (CURSOR (SELECT a_id, assigned_to FROM assignment ORDER BY a_id))); 
+1

La solución fila por fila Plsql puede ser a veces más fácil de leer, pero casi siempre es menos eficiente que el SQL puro. –

+0

También soy muy curioso sobre por qué dices "y más eficiente en el rendimiento: utiliza una función segmentada". ¿Le importaria explicar? –

+0

Es mucho más eficiente que cualquier otro SQL y explicaré por qué. El primer y más importante paso de cualquier diseño de rendimiento no es colocar SQL por delante de PL/SQL sino para minimizar la E/S lógica. La consulta anterior realiza una EXPLORACIÓN SIMPLE de la tabla + una orden por y eso es todo. Cualquier otra consulta al menos tendría que realizar 2 escaneos. P.ej. probablemente exista alguna manera inteligente de usar una función analítica para calcular el 'bloque contiguo' y luego AGRUPAR POR esta nueva columna calculada, pero esto implicaría 2 escaneos + un GRUPO AD adicional. –

1

Aquí es mi sugerencia, no muy bien probado, pero en mi cabeza suena bien, siempre y cuando la identificación es única y una secuencia continua. Mire la parte inferior de la consulta SQL.

SQL> create table ticket (id number, assigned_to varchar2(30)); 

Table created. 

SQL> insert into ticket values (100, 'raju'); 

1 row created. 

SQL> insert into ticket values (101, 'raju'); 

1 row created. 

SQL> insert into ticket values (102, 'raju'); 

1 row created. 

SQL> insert into ticket values (103, 'anil'); 

1 row created. 

SQL> insert into ticket values (104, 'anil'); 

1 row created. 

SQL> insert into ticket values (105, 'sam'); 

1 row created. 

SQL> insert into ticket values (106, 'raju'); 

1 row created. 

SQL> insert into ticket values (107, 'raju'); 

1 row created. 

SQL> insert into ticket values (108, 'anil'); 

1 row created. 

SQL> select a.id from_id 
    2 ,lead(a.id -1, 1, a.id) over (order by a.id) to_id 
    3 ,a.assigned_to 
    4 from (
    5 select 
    6 id, assigned_to 
    7 ,lag(assigned_to, 1) over (order by id) prev_assigned_to 
    8 from ticket 
    9 ) a 
10 where a.assigned_to != nvl(a.prev_assigned_to, a.assigned_to||'unique') 
11 order by id 
12 ; 

    FROM_ID  TO_ID ASSIGNED_TO 
---------- ---------- ------------------------------ 
     100  102 raju 
     103  104 anil 
     105  105 sam 
     106  107 raju 
     108  108 anil 
2

esto debería funcionar. La solución consiste en varias vistas en línea: cada una de ellas calcula algo. Comenté lo que tenía la intención de hacer. Por supuesto, debe leer los comentarios desde lo más profundo a medida que se ejecuta.

--get results by grouping by interval_begin 
SELECT MIN(id) from_id, 
     MAX(id) to_id, 
     MAX(assigned_to) assigned_to 
    FROM (--copy ids of a first row of each interval of ids to the all following rows of that interval 
     SELECT id, 
       assigned_to, 
       MAX(change_at) over(ORDER BY id) interval_begin 
      FROM (--find each id where a change of an assignee occurs and "mark" it. Dont forget the first row 
       SELECT id, 
         assigned_to, 
         CASE 
          WHEN (lag(assigned_to) over(ORDER BY id) <> assigned_to OR lag(assigned_to) 
           over(ORDER BY id) IS NULL) THEN 
          id 
         END change_at 
        FROM ticket)) 
GROUP BY interval_begin 
ORDER BY from_id; 
    ; 
+0

Esto en realidad fue mucho más útil para mí, ya que tenía un problema similar pero un poco más complejo sin el beneficio de una identificación única. Pero no entiendo, ¿por qué funciona el "ORDER BY id" en la consulta "copiar identificadores de una primera fila"? Parece que está tomando el MAX (change_at) para el conjunto en ejecución de todo lo que está en fila hasta ahora (en mi caso dentro de cada partición), pero no creo que las funciones analíticas funcionen de esa manera. – orbfish

7
SQL> create table ticket (id,assigned_to) 
    2 as 
    3 select 100, 'raju' from dual union all 
    4 select 101, 'raju' from dual union all 
    5 select 102, 'raju' from dual union all 
    6 select 103, 'anil' from dual union all 
    7 select 104, 'anil' from dual union all 
    8 select 105, 'sam' from dual union all 
    9 select 106, 'raju' from dual union all 
10 select 107, 'raju' from dual union all 
11 select 108, 'anil' from dual 
12/

Tabel is aangemaakt. 

SQL> select min(id) from_id 
    2  , max(id) to_id 
    3  , assigned_to 
    4 from (select id 
    5    , assigned_to 
    6    , id - row_number() over (partition by assigned_to order by id) grp 
    7    from ticket 
    8  ) 
    9 group by assigned_to 
10  , grp 
11 order by from_id 
12/

    FROM_ID  TO_ID ASSIGNED_TO 
---------- ---------- ----------- 
     100  102 raju 
     103  104 anil 
     105  105 sam 
     106  107 raju 
     108  108 anil 

5 rijen zijn geselecteerd. 

** UPDATE con los resultados de una comparación de rendimiento con la solución de tuinstoel:

En 11.1.0.7:

SQL> exec runstats_pkg.rs_start 

PL/SQL procedure successfully completed. 

SQL> set termout off 
SQL> select min(id) from_id 
    2  , max(id) to_id 
    3  , assigned_to 
    4 from (select id 
    5    , assigned_to 
    6    , id - row_number() over (partition by assigned_to order by id) grp 
    7    from ticket 
    8  ) 
    9 group by assigned_to 
10  , grp 
11 order by from_id 
12/

    FROM_ID  TO_ID ASSI 
---------- ---------- ---- 
     100  102 raju 
     103  104 anil 
     105  105 sam 
     106  107 raju 
     108  108 anil 
     109  111 raju 
<snip> 
    589921  589922 raju 
    589923  589923 anil 

327680 rows selected. 

SQL> set termout on 
SQL> exec runstats_pkg.rs_middle 

PL/SQL procedure successfully completed. 

SQL> set termout off 
SQL> select * from table(testpl.pltest) 
    2/

    FROM_ID  TO_ID ASSI 
---------- ---------- ---- 
     100  102 raju 
     103  104 anil 
     105  105 sam 
     106  107 raju 
     108  108 anil 
     109  111 raju 
<snip> 
    589921  589922 raju 
    589923  589923 anil 

327680 rows selected. 

SQL> set termout on 

y los resultados:

SQL> exec runstats_pkg.rs_stop(100) 
Run1 draaide in 547 hsecs 
Run2 draaide in 549 hsecs 
Run1 draaide in 99.64% van de tijd 

Naam              Run1  Run2 Verschil 
STAT.recursive cpu usage          2   106   104 
LATCH.row cache objects          91   217   126 
STAT.bytes received via SQL*Net from client    37,496  37,256  -240 
STAT.recursive calls           7  5,914  5,907 
STAT.table scan rows gotten       615,235  589,824  -25,411 
STAT.sorts (rows)          917,504  589,824 -327,680 

Run1 latches totaal versus run2 -- verschil en percentage 
Run1  Run2 Verschil  Pct 
10,255 10,471  216 97.94% 

PL/SQL procedure successfully completed. 

Saludos, Rob.

+0

Bastante bueno, es fácil de entender, suponiendo que uno sabe cómo funcionan las funciones analíticas. –

+0

Agradable y simple. No me di cuenta de que en este caso es posible dividir por asignado a igual que grp. Así que utilicé una forma un poco más compleja para calcular "grp" :(+1 Messyss 'es aún mejor en términos de rendimiento (un tipo menos), pero él supone que el ID es denso (dos IDS consecutivos por 1) y nosotros no lo hagas. –

2

he hecho un poco de evaluación comparativa con Oracle Express Edition 10.2.0.1.0

he usado este script para llenar billete mesa con 1179648 filas:

create table ticket (id,assigned_to) 
as 
select 100, 'raju' from dual union all 
select 101, 'raju' from dual union all 
select 102, 'raju' from dual union all 
select 103, 'anil' from dual union all 
select 104, 'anil' from dual union all 
select 105, 'sam' from dual union all 
select 106, 'raju' from dual union all 
select 107, 'raju' from dual union all 
select 108, 'anil' from dual 
/


begin 
    for i in 1..17 loop 
    insert into ticket 
    select id + (select count(*) from ticket), assigned_to 
    from ticket; 
    end loop; 
end; 
/

commit; 

SQL> select count(*) from ticket; 

    COUNT(*)                  
----------                  
    1179648                  
instrucción de selección

Rob van Wijk de toma 1.6 segundos en promedio , La declaración selectiva de Mesays de 2.8 segundos en promedio, declaración de Micheal Pravda de seleccionar 4.2 segundos y Andrew de la declaración de NZSG 9.6 segundos en promedio.

Por lo tanto, una función segmentada es más lenta en Oracle XE. O tal vez alguien tiene que mejorar la función pipeline ...?

+0

+1 Gracias por la prueba! –

3

Digamos que Andrew de NZSG me inspiró. Hice una función de tubería forrada también.

create or replace package testpl is 

type outrec_type is record 
(from_id ticket.id%type 
, to_id ticket.id%type 
, assigned_to ticket.assigned_to%type); 

type outrec_table is table of outrec_type; 

function pltest return outrec_table pipelined; 

end; 
/

create or replace package body testpl is 

    function pltest return outrec_table pipelined 
    is 
    l_outrec outrec_type; 
    l_first_time boolean := true; 
    begin 

    for r_tick in (select id, assigned_to from ticket order by id) loop 

     if (r_tick.assigned_to != l_outrec.assigned_to or l_first_time) then 
      if not l_first_time then 
      pipe row (l_outrec); 
      else 
      l_first_time := false; 
      end if; 
      l_outrec.assigned_to := r_tick.assigned_to; 
      l_outrec.from_id := r_tick.id; 
     end if; 
     l_outrec.to_id := r_tick.id; 
    end loop; 

    pipe row (l_outrec); 

    return; 
    end; 

end; 
/

Puede probarlo con:

select * from table(testpl.pltest); 

Es aproximadamente el doble que la solución de Rob van Wijk en mi sistema Windows XP Oracle 11.1.0.6.0.

La construcción

for r_tick in (select ....) loop 
    .... 
end loop; 

tiene un rendimiento muy decente en Oracle 10 y 11. La mayor parte del tiempo SQL únicas soluciones son más rápidos, pero creo que aquí PL/SQL es más rápido.

+0

+1 buena solución PL/SQL limpia. Sin embargo, veo los mismos tiempos de respuesta en mi prueba. ¿Ajustó su SQL * Plus arraysize a 100? Actualizaré mi publicación con los resultados de esa prueba. –