2014-10-12 12 views
10

Ho una tabella slots come questo:Unire file continue con PostgreSQL

Column |   Type    | 
------------+-----------------------------+ 
id   | integer      | 
begin_at | timestamp without time zone | 
end_at  | timestamp without time zone | 
user_id | integer      | 

e mi piace per selezionare le righe unite per tempo continuo. Diciamo che ho (semplificato) dati come:

(1, 5:15, 5:30, 1) 
(2, 5:15, 5:30, 2) 
(3, 5:30, 5:45, 2) 
(4, 5:45, 6:00, 2) 
(5, 8:15, 8:30, 2) 
(6, 8:30, 8:45, 2) 

Vorrei sapere se è possibile selezionare le righe formattate come:

(5:15, 5:30, 1) 
(5:15, 6:00, 2) // <======= rows id 2,3 and 4 merged 
(8:15, 8:45, 2) // <======= rows id 5 and 6 merged 

EDIT: Ecco il SQLfiddle

Sto usando Postgresql, versione 9.3!

Grazie!

+0

Puoi tranquillizzare i tuoi dati? – Mihai

+0

Domanda simile a SQL Server http://stackoverflow.com/questions/7520556/query-to-merge-continuous-temporal-records – Ram

+0

Utilizzare una query ricorsiva, iniziare da stub iniziali iniziali non affrancabili. Estendi unendo il seguente segmento. Seleziona solo i percorsi più lunghi. – wildplasser

risposta

11

Ecco un metodo per risolvere questo problema. Crea un flag che determina se un record corrisponde a non e si sovrappone a quello precedente. Questo è l'inizio di un gruppo. Poi prendere la somma cumulativa di questo flag e l'uso che per il raggruppamento:

select user_id, min(begin_at) as begin_at, max(end_at) as end_at 
from (select s.*, sum(startflag) over (partition by user_id order by begin_at) as grp 
     from (select s.*, 
        (case when lag(end_at) over (partition by user_id order by begin_at) >= begin_at 
         then 0 else 1 
        end) as startflag 
      from slots s 
      ) s 
    ) s 
group by user_id, grp; 

Here è un violino SQL.

2

Gordon Linoff ha già fornito la risposta (I upvoted).

Ho usato lo stesso approccio, ma volevo occuparmi di tsrange type. Così mi si avvicinò con this construct:

SELECT min(id) b_id, min(begin_at) b_at, max(end_at) e_at, grp, user_id 
    FROM (
    SELECT t.*, sum(g) OVER (ORDER BY id) grp 
     FROM (
     SELECT s.*, (NOT r -|- lag(r,1,r) 
        OVER (PARTITION BY user_id ORDER BY id))::int g 
      FROM (SELECT id,begin_at,end_at,user_id, 
         tsrange(begin_at,end_at,'[)') r FROM slots) s 
    ) t 
) u 
GROUP BY grp, user_id 
ORDER BY grp; 

Purtroppo, al livello superiore si deve usare min(begin_at) e max(end_at), come non esistono nemmeno le funzioni di aggregazione per l'operatore UNION gamma basata +.

Creo intervalli con limiti superiori esclusivi, questo mi consente di utilizzare “is adjacent to” (-|-) operator. Confronto l'attuale tsrange con quello della riga precedente, in modo predefinito a quello corrente nel caso in cui non ci sia precedente. Quindi annullo il confronto e il cast in integer, che mi dà 1 nei casi in cui il nuovo gruppo inizia.