Gruplama veya Pencere


13

Pencere fonksiyonu kullanılarak çözülebileceğini düşündüğüm bir durum var ama emin değilim.

Aşağıdaki tabloyu düşünün

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 )
  ;

İd_type sütununda her değişiklik için yeni bir grup istiyorum. EG 1. grup 7:19:21 - 7:19:25, 2. başlangıç ​​ve bitiş 7:19:26, vb.
Çalıştıktan sonra grupları tanımlamak için daha fazla kriter eklemek istiyorum.

Şu anda, aşağıdaki sorguyu kullanarak ...

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;

Aşağıdaki sonucu alıyorum:

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

Ben isterken:

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

Bu ilk adımı çözdükten sonra, grupları kırmak için kural olarak kullanmak üzere daha fazla sütun ekleyeceğim ve bu diğerleri geçersiz sayılacak.

Postgres Sürümü: 8.4 (Postgis ile Postgresimiz var, bu yüzden yükseltmek kolay değil. postgis 2.x)


Yanıtlar:


4

Birkaç nokta için,

  • tmpSadece kafa karıştırıcı geçici bir tablo demeyin.
  • Zaman damgaları için metin kullanmayın (örneğinizde bunu söyleyebiliriz çünkü zaman damgası kısaltılmamıştır ve vardır .0)
  • İçinde zamanı olan bir alanı arama date. Tarih ve saat varsa, bu bir zaman damgasıdır (ve bir olarak saklayın)

Bir pencere fonksiyonu kullanmak daha iyi ..

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);

çıktılar

 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)

Açıklama

Öncelikle sıfırlamaya ihtiyacımız var .. 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)

Sonra grupları almak için sayıyoruz.

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)

Sonra bir alt seçici sarıyoruz GROUP BYve ORDERmin max (aralık)

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

16

1. Pencere işlevleri artı alt sorgular

Değişiklik ve düzeltmelerle Evan'ın fikrine benzer gruplar oluşturmak için adımları sayın :

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);

Bu, ilgili sütunların olduğunu varsayar NOT NULL. Yoksa daha fazlasını yapman lazım.

Ayrıca datetanımlandığı varsayılarak UNIQUE, ORDER BYmaddelere bir tiebreaker eklemeniz gerekir . Gibi: ORDER BY date, id.

Ayrıntılı açıklama (çok benzer soruya cevap):

Özellikle not:

  • İlgili durumlarda, ilk (veya son) sıranın köşe kasasını zarif bir şekilde kapatmak için lag()3 parametre ile gerekli olabilir. (Önceki (sonraki) satır yoksa 3. parametre varsayılan olarak kullanılır.

    lag(id_type, 1, id_type) OVER ()

    Sadece ( ) ' nin gerçek bir değişikliği ile ilgilendiğimiz için, bu özel durumda önemli değildir. ve ikisi de sayılmaz .id_typeTRUENULLFALSEstep

  • count(step OR NULL) OVER (ORDER BY date)Postgres 9.3 veya daha eski sürümlerde de çalışan en kısa sözdizimidir. count()yalnızca null olmayan değerleri sayar ...

    Modern Postgres'de daha temiz, eşdeğer sözdizimi şöyle olur:

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

    Detaylar:

2. İki pencere işlevi, bir alt sorgu çıkarın

Erik'in değişikliklerle fikrine benzer :

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);

Eğer yukarıda bahsettiğim gibi datetanımlanırsa UNIQUE(asla netleştirmediniz), dense_rank()sonuç için olanla aynı row_number()ve ikincisi önemli ölçüde daha ucuz olduğu için anlamsız olacaktır .

Eğer dateedilir değil tanımlanmış UNIQUE(ve biz sadece çiftleri üzerinde olduğunu bilmiyorum (date, id_type)) sonuç keyfi olduğundan, bütün bu sorgular, anlamsız.

Ayrıca, bir alt sorgu genellikle Postgres'teki bir CTE'den daha ucuzdur. CTE'leri yalnızca ihtiyacınız olduğunda kullanın .

Daha fazla açıklama ile ilgili cevaplar:

Tabloda zaten çalışan bir sayıya sahip olduğumuz ilgili durumlarda, tek bir pencere işleviyle yapabiliriz:

3. plpgsql fonksiyonu ile Üst performans

Bu soru beklenmedik bir şekilde popüler hale geldiğinden, en iyi performansı göstermek için başka bir çözüm ekleyeceğim.

SQL, kısa ve zarif sözdizimi ile çözümler oluşturmak için birçok gelişmiş araca sahiptir. Ancak bildirici bir dilin prosedürel unsurları içeren daha karmaşık gereklilikler için sınırları vardır.

Bir sunucu tarafı prosedürel fonksiyon sadece bir gerektiğinden şey şimdiye kadar yayınlanan daha hızlı bu içindir tek sıralı tarama masası ve üzerinde tek çeşit operasyonu . Uygun bir dizin varsa, yalnızca tek bir dizin taraması bile.

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;

Aramak:

SELECT * FROM f_tmp_groups();

Şununla test et:

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

Fonksiyonu polimorfik tiplerle genel hale getirebilir ve tablo tipi ile sütun adlarını geçirebilirsiniz. Detaylar:

Bunun için bir işlev sürdürmek istemezseniz veya devam edemezseniz, geçici bir işlev oluşturmak için bile ödeme yaparsınız. Birkaç ms maliyeti.


dbfiddle Postgres 9.6 için, tüm three.Building için performans karşılaştırmasıJack'in test durumu, modifiye.

Performans farklılıklarının daha da büyük olduğuPostgres 8.4 için dbfiddle .


Bunu birkaç kez okuyun - üç argüman gecikmesinden ne bahsettiğinizden veya ne zaman kullanmanız gerektiğinden count(x or null)veya orada ne yaptığından emin değilsiniz . Belki de bazı örnekler gösterilebilir edilir burada gerekli değildir, çünkü gerekli. Ve bu köşe davalarını kapsamak için gereklilik ne olabilir? BTW, ben plv / pgsql örneği için benim downvote upvote değiştirdim. Bu gerçekten havalı. (Ancak, genellikle diğer cevapları özetleyen veya köşe vakalarını kapsayan cevaplara karşıyım - bunun bir köşe davası olduğunu söylemekten nefret ediyorum, çünkü anlamıyorum.)
Evan Carroll

Onları iki ayrı cevaplı soruya koyardım çünkü ne count(x or null)yapacağını merak eden tek kişi olmadığımdan eminim . İsterseniz her iki soruyu da sormaktan mutluluk duyarım.
Evan Carroll


7

Bunu basit bir ROW_NUMBER()işlem çıkarma olarak yapabilirsiniz (veya tarihleriniz benzersiz olmasa da, benzersiz olsa da, daha pahalı bir sorgu olsa da id_typekullanabilirsiniz DENSE_RANK()):

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
;

Bu çalışmaya DB Fiddle'da bakın (veya DENSE_RANK sürümüne bakın )

Sonuç:

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

Mantıken, bir basit olarak bu düşünebiliriz DENSE_RANK()bir ile PREORDER BYolduğunu, istediğiniz DENSE_RANKbirlikte sıralanır tüm öğelerin ve bunları tarihlerine göre sıralı istiyorum, sadece gerçeği Bunun sinir bozucu sorunla başa çıkmak zorunda tarihte her değişiklik, DENSE_RANKartacaktır. Bunu, yukarıda gösterdiğim gibi ifadeyi kullanarak yaparsınız. Şu sözdizimine sahip olup olmadığınızı düşünün: DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)burada PREORDERsıralama hesaplamasının dışında bırakılır ve yalnızca ORDER BYsayılır.

GROUP BYHem üretilen Seqsütun hem de sütun için önemli olduğunu unutmayın id_type. Seqtek başına benzersiz DEĞİLDİR, çakışmalar olabilir - ayrıca gruplandırmalısınız id_type.

Bu konuyla ilgili daha fazla bilgi için:

Bu ilk bağlantı, başlangıç ​​veya bitiş tarihinin önceki veya sonraki dönemin bitiş / başlangıç ​​tarihiyle aynı olmasını istiyorsanız kullanabileceğiniz bazı kodlar verir (bu nedenle boşluk yoktur). Ayrıca sorgunuzda size yardımcı olabilecek diğer sürümler. SQL Server sözdiziminden çevrilmiş olmalarına rağmen ...


6

Postgres 8.4'te bir RECURSIVE işlevi kullanabilirsiniz .

Nasıl yapıyorlar

Özyinelemeli işlev, azalan sırada tarihleri ​​tek tek seçerek her farklı id_type için bir seviye ekler.

       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

Ardından istenen sonucu elde etmek için MAX (tarih), MIN (tarih) düzeyine göre gruplama, id_type kullanın.

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    |
+---------------------+---------------------+---------+

Kontrol edin: http://rextester.com/WCOYFP6623


5

İşte adaları belirlemek için LAG kullandığı için Evan ve Erwin'inkine benzeyen başka bir yöntem. Yalnızca tek bir düzey iç içe yerleştirme, gruplandırma ve önemli ölçüde daha fazla pencere işlevi kullanması nedeniyle bu çözümlerden farklıdır:

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
;

is_startİç içe SELECT içindeki hesaplanan sütun, her adanın başlangıcını işaretler. Ayrıca, iç içe SELECT her satırın önceki tarihini ve veri kümesinin son tarihini gösterir.

İlgili adalarının başlangıcı olan satırlar için, önceki tarih etkili bir şekilde önceki adanın bitiş tarihidir. Ana SELECT bunu kullanır. Bu eşleme sadece satırları seçer is_start = 1koşulu ve her döndü satır için o satır kendi gösterir dateşekilde beginve aşağıdaki satır var prev_dateolarak end. Son satırda aşağıdaki satır bulunmadığından LEAD(prev_date), bunun için COALESCE işlevinin veri kümesinin son tarihinin yerini aldığı bir null değeri döndürür.

Bu çözümle dbfiddle'da oynayabilirsiniz .

Adaları tanımlayan ek sütunlar eklenirken, büyük olasılıkla her pencere işlevinin OVER yan tümcesine bir PARTITION BY alt bölümü eklemek isteyeceksiniz. Örneğin, a ile tanımlanan gruplar içindeki adaları tespit etmek istiyorsanız parent_id, yukarıdaki sorgunun muhtemelen şöyle görünmesi gerekecektir:

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
;

Ve Erwin'in ya da Evan'ın çözümüne karar verirseniz, buna benzer bir değişikliğin de ekleneceğine inanıyorum.


5

Akademik bir ilgiden pratik bir çözüm olmaktan çok, bunu kullanıcı tanımlı bir kümeyle de başarabilirsiniz . Diğer çözümler gibi, bu Postgres 8.4'te bile çalışacaktır, ancak diğerlerinin yorumladığı gibi, lütfen mümkünse yükseltin.

Agrega null, sanki farklıymış gibi işler foo_type, bu nedenle null'lar aynı şekilde verilir grp- bu, istediğiniz gibi olabilir veya olmayabilir.

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 | 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

dbfiddle burada


4

Bu, RECURSIVE CTE"başlangıç ​​zamanını" bir satırdan diğerine geçirmek için ve bazı ekstra (kolaylık) hazırlıklar ile yapılabilir.

Bu sorgu istediğiniz sonucu döndürür:

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") 
)

hazırlandıktan sonra ... özyinelemeli kısım

, 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 ;

Bunu http://rextester.com/POYM83542 adresinden kontrol edebilirsiniz.

Bu yöntem iyi ölçeklenmiyor. 8_641 satırlık bir tablo için 7s alır, bu boyutun iki katı bir tablo için 28s sürer. Birkaç örnek daha O (n ^ 2) gibi görünen yürütme sürelerini gösterir.

Evan Carrol'un yöntemi 1 saniyeden daha az sürer (yani: git!) Ve O (n) gibi görünüyor. Yinelemeli sorgular kesinlikle verimsizdir ve son çare olarak düşünülmelidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.