Pengelompokan atau Jendela

13

Saya memiliki situasi yang saya pikir dapat diselesaikan menggunakan fungsi jendela tapi saya tidak yakin.

Bayangkan tabel berikut

CREATE TABLE tmp
  ( date timestamp,        
    id_type integer
  ) ;

INSERT INTO tmp 
    ( date, id_type )
VALUES
    ( '2017-01-10 07:19:21.0', 3 ),
    ( '2017-01-10 07:19:22.0', 3 ),
    ( '2017-01-10 07:19:23.1', 3 ),
    ( '2017-01-10 07:19:24.1', 3 ),
    ( '2017-01-10 07:19:25.0', 3 ),
    ( '2017-01-10 07:19:26.0', 5 ),
    ( '2017-01-10 07:19:27.1', 3 ),
    ( '2017-01-10 07:19:28.0', 5 ),
    ( '2017-01-10 07:19:29.0', 5 ),
    ( '2017-01-10 07:19:30.1', 3 ),
    ( '2017-01-10 07:19:31.0', 5 ),
    ( '2017-01-10 07:19:32.0', 3 ),
    ( '2017-01-10 07:19:33.1', 5 ),
    ( '2017-01-10 07:19:35.0', 5 ),
    ( '2017-01-10 07:19:36.1', 5 ),
    ( '2017-01-10 07:19:37.1', 5 )
  ;

Saya ingin memiliki grup baru di setiap perubahan pada kolom id_type. EG Grup 1 dari 7:19:21 hingga 7:19:25, ke-2 dimulai dan berakhir pada 7:19:26, dan seterusnya.
Setelah berhasil, saya ingin memasukkan lebih banyak kriteria untuk mendefinisikan grup.

Saat ini, gunakan kueri di bawah ...

SELECT distinct 
    min(min(date)) over w as begin, 
    max(max(date)) over w as end,   
    id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by  begin;

Saya mendapatkan hasil sebagai berikut:

begin                   end                     id_type
2017-01-10 07:19:21.0   2017-01-10 07:19:32.0   3
2017-01-10 07:19:26.0   2017-01-10 07:19:37.1   5

Sementara saya suka:

begin                   end                     id_type
2017-01-10 07:19:21.0   2017-01-10 07:19:25.0   3
2017-01-10 07:19:26.0   2017-01-10 07:19:26.0   5
2017-01-10 07:19:27.1   2017-01-10 07:19:27.1   3
2017-01-10 07:19:28.0   2017-01-10 07:19:29.0   5
2017-01-10 07:19:30.1   2017-01-10 07:19:30.1   3
2017-01-10 07:19:31.0   2017-01-10 07:19:31.0   5
2017-01-10 07:19:32.0   2017-01-10 07:19:32.0   3
2017-01-10 07:19:33.1   2017-01-10 07:19:37.1   5

Setelah saya menyelesaikan langkah pertama ini, saya akan menambahkan lebih banyak kolom untuk digunakan sebagai aturan untuk memecah grup, dan yang lainnya akan menjadi nol.

Versi Postgres: 8.4 (Kami memiliki Postgres dengan Postgis, jadi tidak mudah untuk ditingkatkan. Fungsi Postgis mengubah nama dan ada masalah lain, tapi mudah-mudahan kami sudah menulis semuanya dan versi baru akan menggunakan versi yang lebih baru 9.X dengan postgis 2.x)

Lelo
sumber
2
Solusi umum: dba.stackexchange.com/questions/35380/…
Erwin Brandstetter

Jawaban:

4

Untuk beberapa poin,

  • Jangan panggil tabel non-temporer tmpyang membingungkan.
  • Jangan gunakan teks untuk cap waktu (Anda melakukan itu dalam contoh Anda, kami dapat memberitahu karena cap waktu tidak terpotong dan memiliki .0)
  • Jangan panggil bidang yang memiliki waktu di dalamnya date. Jika memiliki tanggal dan waktu, itu adalah cap waktu (dan simpan sebagai satu)

Lebih baik menggunakan fungsi jendela ..

SELECT id_type, grp, min(date), max(date)
FROM (
  SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
  FROM (
    SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
    FROM tmp
  ) AS t
) AS g
GROUP BY id_type, grp
ORDER BY min(date);

Keluaran

 id_type | grp |          min          |          max          
---------+-----+-----------------------+-----------------------
       3 |   0 | 2017-01-10 07:19:21.0 | 2017-01-10 07:19:25.0
       5 |   1 | 2017-01-10 07:19:26.0 | 2017-01-10 07:19:26.0
       3 |   2 | 2017-01-10 07:19:27.1 | 2017-01-10 07:19:27.1
       5 |   3 | 2017-01-10 07:19:28.0 | 2017-01-10 07:19:29.0
       3 |   4 | 2017-01-10 07:19:30.1 | 2017-01-10 07:19:30.1
       5 |   5 | 2017-01-10 07:19:31.0 | 2017-01-10 07:19:31.0
       3 |   6 | 2017-01-10 07:19:32.0 | 2017-01-10 07:19:32.0
       5 |   7 | 2017-01-10 07:19:33.1 | 2017-01-10 07:19:37.1
(8 rows)

Penjelasan

Pertama kita perlu me-reset .. Kita menghasilkannya dengan lag()

SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
FROM tmp
ORDER BY date;

         date          | id_type | is_reset 
-----------------------+---------+----------
 2017-01-10 07:19:21.0 |       3 |         
 2017-01-10 07:19:22.0 |       3 |         
 2017-01-10 07:19:23.1 |       3 |         
 2017-01-10 07:19:24.1 |       3 |         
 2017-01-10 07:19:25.0 |       3 |         
 2017-01-10 07:19:26.0 |       5 |        1
 2017-01-10 07:19:27.1 |       3 |        1
 2017-01-10 07:19:28.0 |       5 |        1
 2017-01-10 07:19:29.0 |       5 |         
 2017-01-10 07:19:30.1 |       3 |        1
 2017-01-10 07:19:31.0 |       5 |        1
 2017-01-10 07:19:32.0 |       3 |        1
 2017-01-10 07:19:33.1 |       5 |        1
 2017-01-10 07:19:35.0 |       5 |         
 2017-01-10 07:19:36.1 |       5 |         
 2017-01-10 07:19:37.1 |       5 |         
(16 rows)

Lalu kami menghitung untuk mendapatkan grup.

SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
FROM (
  SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
  FROM tmp
  ORDER BY date
) AS t
ORDER BY date

         date          | id_type | grp 
-----------------------+---------+-----
 2017-01-10 07:19:21.0 |       3 |   0
 2017-01-10 07:19:22.0 |       3 |   0
 2017-01-10 07:19:23.1 |       3 |   0
 2017-01-10 07:19:24.1 |       3 |   0
 2017-01-10 07:19:25.0 |       3 |   0
 2017-01-10 07:19:26.0 |       5 |   1
 2017-01-10 07:19:27.1 |       3 |   2
 2017-01-10 07:19:28.0 |       5 |   3
 2017-01-10 07:19:29.0 |       5 |   3
 2017-01-10 07:19:30.1 |       3 |   4
 2017-01-10 07:19:31.0 |       5 |   5
 2017-01-10 07:19:32.0 |       3 |   6
 2017-01-10 07:19:33.1 |       5 |   7
 2017-01-10 07:19:35.0 |       5 |   7
 2017-01-10 07:19:36.1 |       5 |   7
 2017-01-10 07:19:37.1 |       5 |   7
(16 rows)

Kemudian kita bungkus dalam subselect GROUP BYdan ORDERdan pilih min max (range)

SELECT id_type, grp, min(date), max(date)
FROM (
  .. stuff
) AS g
GROUP BY id_type, grp
ORDER BY min(date);
Evan Carroll
sumber
16

1. Fungsi jendela plus subqueries

Hitung langkah untuk membentuk grup, mirip dengan ide Evan , dengan modifikasi dan perbaikan:

SELECT id_type
     , min(date) AS begin
     , max(date) AS end
     , count(*)  AS row_ct  -- optional addition
FROM  (
   SELECT date, id_type, count(step OR NULL) OVER (ORDER BY date) AS grp
   FROM  (
      SELECT date, id_type
           , lag(id_type, 1, id_type) OVER (ORDER BY date) <> id_type AS step
      FROM   tmp
      ) sub1
   ) sub2
GROUP  BY id_type, grp
ORDER  BY min(date);

Ini mengasumsikan kolom yang terlibat adalah NOT NULL. Lain yang perlu Anda lakukan lebih banyak.

Juga dengan asumsi datedidefinisikan UNIQUE, kalau tidak Anda perlu menambahkan tiebreaker ke ORDER BYklausa mendapatkan hasil deterministik. Seperti: ORDER BY date, id.

Penjelasan terperinci (jawaban untuk pertanyaan yang sangat mirip):

Catatan khususnya:

  • Dalam kasus terkait, lag()dengan 3 parameter dapat menjadi penting untuk menutupi kasus sudut baris pertama (atau terakhir) secara elegan. (Param 3 digunakan sebagai default jika tidak ada baris sebelumnya (berikutnya).

    lag(id_type, 1, id_type) OVER ()

    Karena kita hanya tertarik dalam sebenarnya perubahan dari id_type( TRUE), tidak peduli dalam kasus ini. NULLdan FALSEkeduanya tidak masuk hitungan step.

  • count(step OR NULL) OVER (ORDER BY date)adalah sintaks terpendek yang juga berfungsi di Postgres 9.3 atau lebih lama. count()hanya menghitung nilai yang bukan nol ...

    Dalam Postgres modern, sintaks yang lebih bersih dan setara adalah:

    count(step) FILTER (WHERE step) OVER (ORDER BY date)

    Detail:

2. Kurangi dua fungsi jendela, satu subquery

Mirip dengan ide Erik dengan modifikasi:

SELECT min(date) AS begin
     , max(date) AS end
     , id_type
FROM  (
   SELECT date, id_type
        , row_number() OVER (ORDER BY date)
        - row_number() OVER (PARTITION BY id_type ORDER BY date) AS grp
   FROM   tmp
   ) sub
GROUP  BY id_type, grp
ORDER  BY min(date);

Jika datedidefinisikan UNIQUE, seperti yang saya sebutkan di atas (Anda tidak pernah mengklarifikasi), dense_rank()akan sia-sia, karena hasilnya sama dengan row_number()dan yang terakhir jauh lebih murah.

Jika dateini tidak didefinisikan UNIQUE(dan kita tidak tahu bahwa satu-satunya duplikat berada di (date, id_type)), semua pertanyaan ini adalah sia-sia, karena hasilnya adalah sewenang-wenang.

Juga, sebuah subquery biasanya lebih murah daripada CTE di Postgres. Gunakan CTE hanya saat Anda membutuhkannya .

Jawaban terkait dengan penjelasan lebih lanjut:

Dalam kasus terkait di mana kita sudah memiliki nomor berjalan dalam tabel, kita dapat puas dengan fungsi satu jendela:

3. Kinerja terbaik dengan fungsi plpgsql

Karena pertanyaan ini menjadi sangat populer, saya akan menambahkan solusi lain untuk menunjukkan kinerja terbaik.

SQL memiliki banyak alat canggih untuk menciptakan solusi dengan sintaksis pendek dan elegan. Tetapi bahasa deklaratif memiliki keterbatasan untuk persyaratan yang lebih kompleks yang melibatkan elemen prosedural.

Sebuah fungsi prosedural server-side lebih cepat untuk ini dari apa yang diposting sejauh karena hanya membutuhkan scan sekuensial tunggal atas meja dan operasi semacam tunggal . Jika indeks pas tersedia, bahkan hanya pemindaian indeks saja.

CREATE OR REPLACE FUNCTION f_tmp_groups()
  RETURNS TABLE (id_type int, grp_begin timestamp, grp_end timestamp) AS
$func$
DECLARE
   _row  tmp;                       -- use table type for row variable
BEGIN
   FOR _row IN
      TABLE tmp ORDER BY date       -- add more columns to make order deterministic
   LOOP
      CASE _row.id_type = id_type 
      WHEN TRUE THEN                -- same group continues
         grp_end := _row.date;      -- remember last date so far
      WHEN FALSE THEN               -- next group starts
         RETURN NEXT;               -- return result for last group
         id_type   := _row.id_type;
         grp_begin := _row.date;
         grp_end   := _row.date;
      ELSE                          -- NULL for 1st row
         id_type   := _row.id_type; -- remember row data for starters
         grp_begin := _row.date;
         grp_end   := _row.date;
      END CASE;
   END LOOP;

   RETURN NEXT;                     -- return last result row      
END
$func$ LANGUAGE plpgsql;

Panggilan:

SELECT * FROM f_tmp_groups();

Uji dengan:

EXPLAIN (ANALYZE, TIMING OFF)  -- to focus on total performance
SELECT * FROM  f_tmp_groups();

Anda bisa membuat fungsi generik dengan tipe polimorfik dan meneruskan tipe tabel dan nama kolom. Detail:

Jika Anda tidak ingin atau tidak dapat mempertahankan fungsi untuk ini, itu bahkan akan membayar untuk membuat fungsi sementara dengan cepat. Biaya beberapa ms.


dbfiddle untuk Postgres 9.6, membandingkan kinerja untuk ketiganya. Membangun di test case Jack , dimodifikasi.

dbfiddle untuk Postgres 8.4, di mana perbedaan kinerja bahkan lebih besar.

Erwin Brandstetter
sumber
Baca ini beberapa kali - masih tidak yakin dengan apa yang Anda bicarakan dengan lag tiga argumen atau kapan Anda harus menggunakan count(x or null)atau bahkan apa yang dilakukannya di sana. Mungkin Anda bisa menunjukkan beberapa sampel mana yang diperlukan, karena itu tidak diperlukan di sini. Dan, apa yang akan kunci persyaratan untuk menutup kasus-kasus sudut itu. BTW, saya mengubah downvote saya ke upvote hanya untuk contoh pl / pgsql. Itu keren sekali. (Tapi, umumnya saya menentang jawaban yang merangkum jawaban lain atau menutup kasus sudut - meskipun saya benci mengatakan bahwa ini adalah kasus sudut karena saya tidak memahaminya).
Evan Carroll
Saya akan menempatkan mereka dalam dua pertanyaan yang dijawab sendiri secara terpisah karena saya yakin saya bukan satu-satunya yang bertanya-tanya apa yang count(x or null)terjadi. Saya akan dengan senang hati menanyakan kedua pertanyaan jika Anda mau.
Evan Carroll
7

Anda dapat melakukan ini sebagai pengurangan ROW_NUMBER()operasi sederhana (atau jika tanggal Anda tidak unik, meskipun masih unik id_type, maka Anda dapat menggunakannya DENSE_RANK(), meskipun itu akan menjadi permintaan yang lebih mahal):

WITH IdTypes AS (
   SELECT
      date,
      id_type,
      Row_Number() OVER (ORDER BY date)
         - Row_Number() OVER (PARTITION BY id_type ORDER BY date)
         AS Seq
   FROM
      tmp
)
SELECT
   Min(date) AS begin,
   Max(date) AS end,
   id_type
FROM IdTypes
GROUP BY id_type, Seq
ORDER BY begin
;

Lihat karya ini di DB Fiddle (atau lihat versi DENSE_RANK )

Hasil:

begin                  end                    id_type
---------------------  ---------------------  -------
2017-01-10 07:19:21    2017-01-10 07:19:25    3
2017-01-10 07:19:26    2017-01-10 07:19:26    5
2017-01-10 07:19:27.1  2017-01-10 07:19:27.1  3
2017-01-10 07:19:28    2017-01-10 07:19:29    5
2017-01-10 07:19:30.1  2017-01-10 07:19:30.1  3
2017-01-10 07:19:31    2017-01-10 07:19:31    5
2017-01-10 07:19:32    2017-01-10 07:19:32    3
2017-01-10 07:19:33.1  2017-01-10 07:19:37.1  5

Secara logis, Anda dapat menganggap ini sebagai sederhana DENSE_RANK()dengan PREORDER BY, yaitu, Anda menginginkan DENSE_RANKsemua item yang diperingkat bersama, dan Anda ingin mereka dipesan berdasarkan tanggal, Anda hanya perlu berurusan dengan masalah sial dari kenyataan bahwa pada setiap perubahan tanggal, DENSE_RANKakan bertambah. Anda melakukannya dengan menggunakan ekspresi seperti yang saya tunjukkan di atas. Bayangkan jika Anda memiliki sintaks ini: di DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)mana PREORDERitu dikecualikan dari perhitungan peringkat dan hanyaORDER BY dihitung.

Perhatikan bahwa penting GROUP BYbaik untuk Seqkolom yang dibuat maupun id_typekolom. SeqTIDAK unik dengan sendirinya, mungkin ada tumpang tindih - Anda juga harus mengelompokkan berdasarkanid_type .

Untuk bacaan lebih lanjut tentang topik ini:

Tautan pertama itu memberi Anda beberapa kode yang dapat Anda gunakan jika Anda ingin tanggal mulai atau berakhir sama dengan tanggal akhir / mulai periode sebelumnya atau berikutnya (sehingga tidak ada celah). Plus versi lain yang dapat membantu Anda dalam permintaan Anda. Meskipun mereka harus diterjemahkan dari sintaks SQL Server ...

ErikE
sumber
6

Pada Postgres 8.4 Anda dapat menggunakan RECURSIVE fungsi .

Bagaimana mereka melakukannya

Fungsi rekursif menambahkan level ke setiap id_type yang berbeda, dengan memilih tanggal satu per satu pada urutan menurun.

       date           | id_type | lv
--------------------------------------
2017-01-10 07:19:21.0      3       8
2017-01-10 07:19:22.0      3       8
2017-01-10 07:19:23.1      3       8
2017-01-10 07:19:24.1      3       8
2017-01-10 07:19:25.0      3       8
2017-01-10 07:19:26.0      5       7
2017-01-10 07:19:27.1      3       6
2017-01-10 07:19:28.0      5       5
2017-01-10 07:19:29.0      5       5
2017-01-10 07:19:30.1      3       4
2017-01-10 07:19:31.0      5       3
2017-01-10 07:19:32.0      3       2
2017-01-10 07:19:33.1      5       1
2017-01-10 07:19:35.0      5       1
2017-01-10 07:19:36.1      5       1
2017-01-10 07:19:37.1      5       1

Kemudian gunakan MAX (tanggal), MIN (tanggal) yang dikelompokkan berdasarkan level, id_type untuk mendapatkan hasil yang diinginkan.

with RECURSIVE rdates as 
(
    (select   date, id_type, 1 lv 
     from     yourTable
     order by date desc
     limit 1
    )
    union
    (select    d.date, d.id_type,
               case when r.id_type = d.id_type 
                    then r.lv 
                    else r.lv + 1 
               end lv    
    from       yourTable d
    inner join rdates r
    on         d.date < r.date
    order by   date desc
    limit      1)
)
select   min(date) StartDate,
         max(date) EndDate,
         id_type
from     rdates
group by lv, id_type
;

+---------------------+---------------------+---------+
| startdate           |       enddate       | id_type |
+---------------------+---------------------+---------+
| 10.01.2017 07:19:21 | 10.01.2017 07:19:25 |    3    |
| 10.01.2017 07:19:26 | 10.01.2017 07:19:26 |    5    |
| 10.01.2017 07:19:27 | 10.01.2017 07:19:27 |    3    |
| 10.01.2017 07:19:28 | 10.01.2017 07:19:29 |    5    |
| 10.01.2017 07:19:30 | 10.01.2017 07:19:30 |    3    |
| 10.01.2017 07:19:31 | 10.01.2017 07:19:31 |    5    |
| 10.01.2017 07:19:32 | 10.01.2017 07:19:32 |    3    |
| 10.01.2017 07:19:33 | 10.01.2017 07:19:37 |    5    |
+---------------------+---------------------+---------+

Lihatlah: http://rextester.com/WCOYFP6623

McNets
sumber
5

Berikut adalah metode lain, yang mirip dengan Evan dan Erwin karena menggunakan LAG untuk menentukan pulau. Ini berbeda dari solusi-solusi tersebut karena hanya menggunakan satu tingkat fungsi bersarang, tanpa pengelompokan, dan fungsi jendela yang jauh lebih banyak:

SELECT
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      id_type,
      date,
      LAG(date) OVER (ORDER BY date ASC) AS prev_date,
      MAX(date) OVER () AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

The is_startkolom dihitung dalam tanda SELECT bersarang awal setiap pulau. Selain itu, SELECT bersarang memaparkan tanggal sebelumnya setiap baris dan tanggal terakhir dataset.

Untuk baris yang merupakan awal dari masing-masing pulau, tanggal sebelumnya secara efektif adalah tanggal berakhir pulau sebelumnya. Itulah yang SELECT utama gunakan sebagai. Ini hanya mengambil baris yang cocok dengan is_start = 1kondisi, dan untuk setiap baris yang dikembalikan itu menunjukkan baris datesebagai begindan baris berikut prev_datesebagai end. Karena baris terakhir tidak memiliki baris berikut, LEAD(prev_date)mengembalikan nol untuknya, yang fungsi COALESCE menggantikan tanggal terakhir dataset.

Anda dapat bermain dengan solusi ini di dbfiddle .

Saat memperkenalkan kolom tambahan yang mengidentifikasi pulau-pulau, Anda mungkin ingin memperkenalkan PARTITION BY subclause ke OVER klausa setiap fungsi jendela. Misalnya, jika Anda ingin mendeteksi pulau di dalam grup yang ditentukan oleh a parent_id, kueri di atas mungkin perlu terlihat seperti ini:

SELECT
  parent_id,
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (PARTITION BY parent_id ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      parent_id,
      id_type,
      date,
      LAG(date) OVER (PARTITION BY parent_id ORDER BY date ASC) AS prev_date,
      MAX(date) OVER (PARTITION BY parent_id) AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (PARTITION BY parent_id ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

Dan jika Anda memutuskan untuk menggunakan solusi Erwin atau Evan, saya percaya perubahan yang sama perlu ditambahkan ke dalamnya juga.

Andriy M
sumber
5

Lebih dari kepentingan akademis daripada sebagai solusi praktis, Anda juga dapat mencapai ini dengan agregat yang ditentukan pengguna . Seperti solusi lain, ini akan berfungsi bahkan pada Postgres 8.4, tetapi seperti yang telah dikomentari orang lain, silakan tingkatkan jika Anda bisa.

Agregat menangani nullseolah-olah itu berbeda foo_type, sehingga menjalankan nol akan diberikan sama grp- yang mungkin atau mungkin tidak seperti yang Anda inginkan.

create function grp_sfunc(integer[],integer) returns integer[] language sql as $$
  select array[$1[1]+($1[2] is distinct from $2 or $1[3]=0)::integer,$2,1];
$$;
create function grp_finalfunc(integer[]) returns integer language sql as $$
  select $1[1];
$$;
create aggregate grp(integer)(
  sfunc = grp_sfunc
, stype = integer[]
, finalfunc = grp_finalfunc
, initcond = '{0,0,0}'
);
select min(foo_at) begin_at, max(foo_at) end_at, foo_type
from (select *, grp(foo_type) over (order by foo_at) from foo) z
group by grp, foo_type
order by 1;
begin_at | end_at | jenis foo_type
: -------------------- | : -------------------- | -------:
2017-01-10 07:19:21 | 2017-01-10 07:19:25 | 3
2017-01-10 07:19:26 | 2017-01-10 07:19:26 | 5
2017-01-10 07: 19: 27.1 | 2017-01-10 07: 19: 27.1 | 3
2017-01-10 07:19:28 | 2017-01-10 07:19:29 | 5
2017-01-10 07: 19: 30.1 | 2017-01-10 07: 19: 30.1 | 3
2017-01-10 07:19:31 | 2017-01-10 07:19:31 | 5
2017-01-10 07:19:32 | 2017-01-10 07:19:32 | 3
2017-01-10 07: 19: 33.1 | 2017-01-10 07: 19: 37.1 | 5

Aku di sini

Jack berkata coba topanswers.xyz
sumber
4

Ini dapat dilakukan dengan RECURSIVE CTEmelewatkan "waktu mulai" dari satu baris ke yang berikutnya, dan beberapa persiapan (kemudahan) tambahan.

Kueri ini mengembalikan hasil yang Anda inginkan:

WITH RECURSIVE q AS
(
    SELECT
        id_type,
        "date",
        /* We compute next id_type for convenience, plus row_number */
        row_number()  OVER (w) AS rn,
        lead(id_type) OVER (w) AS next_id_type
    FROM
        t
    WINDOW
        w AS (ORDER BY "date") 
)

setelah persiapan ... bagian rekursif

, rec AS 
(
    /* Anchor */
    SELECT
        q.rn,
        q."date" AS "begin",
        /* When next_id_type is different from Look also at **next** row to find out whether we need to mark an end */
        case when q.id_type is distinct from q.next_id_type then q."date" END AS "end",
        q.id_type
    FROM
        q
    WHERE
        rn = 1

    UNION ALL

    /* Loop */
    SELECT
        q.rn,
        /* We keep copying 'begin' from one row to the next while type doesn't change */
        case when q.id_type = rec.id_type then rec.begin else q."date" end AS "begin",
        case when q.id_type is distinct from q.next_id_type then q."date" end AS "end",
        q.id_type
    FROM
        rec
        JOIN q ON q.rn = rec.rn+1
)
-- We filter the rows where "end" is not null, and project only needed columns
SELECT
    "begin", "end", id_type
FROM
    rec
WHERE
    "end" is not null ;

Anda dapat memeriksanya di http://rextester.com/POYM83542

Metode ini tidak skala dengan baik. Untuk tabel baris 8_641, dibutuhkan 7s, untuk tabel dua kali ukurannya, dibutuhkan 28s. Beberapa sampel lagi menunjukkan waktu eksekusi yang tampak seperti O (n ^ 2).

Metode Evan Carrol membutuhkan waktu kurang dari 1 (yaitu: lakukan saja!), Dan terlihat seperti O (n). Permintaan rekursif benar-benar tidak efisien, dan harus dianggap sebagai upaya terakhir.

joanolo
sumber