การจัดกลุ่มหรือหน้าต่าง


13

ฉันมีสถานการณ์ที่ฉันคิดว่าสามารถแก้ไขได้โดยใช้ฟังก์ชั่นหน้าต่าง แต่ฉันไม่แน่ใจ

ลองนึกภาพตารางต่อไปนี้

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

ฉันต้องการมีกลุ่มใหม่ในแต่ละการเปลี่ยนแปลงในคอลัมน์ id_type กลุ่มที่ 1 EG จาก 7:19:21 ถึง 7:19:25, การเริ่มต้นครั้งที่ 2 และสิ้นสุดที่ 7:19:26 เป็นต้นไป
หลังจากทำงานแล้วฉันต้องการรวมเกณฑ์เพิ่มเติมเพื่อกำหนดกลุ่ม

ในขณะนี้ใช้การค้นหาด้านล่าง ...

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;

ฉันได้รับผลลัพธ์ต่อไปนี้:

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

ในขณะที่ฉันต้องการ:

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

หลังจากที่ฉันแก้ไขขั้นตอนแรกฉันจะเพิ่มคอลัมน์เพิ่มเติมเพื่อใช้เป็นกฎในการแบ่งกลุ่มและอื่น ๆ เหล่านี้จะเป็นโมฆะ

รุ่น Postgres: 8.4 (เรามี Postgres ด้วย Postgis ดังนั้นจึงไม่ง่ายที่จะอัพเกรด Postgis ฟังก์ชั่นเปลี่ยนชื่อและมีปัญหาอื่น ๆ แต่หวังว่าเราจะเขียนทุกอย่างแล้วและเวอร์ชั่นใหม่จะใช้เวอร์ชั่นใหม่ 9.X ด้วย postgis 2.x)


2
วิธีแก้ปัญหาทั่วไป: dba.stackexchange.com/questions/35380/…
Erwin Brandstetter

คำตอบ:


4

สำหรับคะแนนสองสาม

  • อย่าเรียกตารางtmpที่ไม่ใช่ชั่วคราวที่เพิ่งเกิดความสับสน
  • อย่าใช้ข้อความสำหรับการประทับเวลา (คุณกำลังทำเช่นนั้นในตัวอย่างของคุณเราสามารถบอกได้เพราะการประทับเวลาไม่ได้ถูกตัดทอนและมี.0)
  • dateอย่าเรียกข้อมูลที่มีเวลาอยู่ในนั้น ถ้ามันมีวันที่และเวลามันเป็นเวลา (และเก็บไว้เป็นหนึ่ง)

ดีกว่าที่จะใช้ฟังก์ชั่นหน้าต่าง ..

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

เอาท์พุท

 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)

ชี้แจง

ก่อนอื่นเราต้องรีเซ็ต .. เราสร้างมันขึ้นมาด้วย 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)

จากนั้นเราก็นับรวมกลุ่ม

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)

จากนั้นเราก็ห่อใน subselect GROUP BYและORDERและเลือกสูงสุดนาที (ช่วง)

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

16

1. ฟังก์ชั่นหน้าต่างรวมทั้งแบบสอบถามย่อย

นับขั้นตอนในการจัดกลุ่มคล้ายกับแนวคิดของ Evanด้วยการแก้ไขและการแก้ไข:

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

NOT NULLคอลัมน์นี้จะถือว่ามีส่วนร่วม อื่นคุณต้องทำเพิ่มเติม

นอกจากนี้สมมติว่าdateมีการกำหนดUNIQUEอื่น ๆ ที่คุณต้องเพิ่ม tiebreaker ในส่วนORDER BYคำสั่งจะได้รับผลลัพธ์ที่กำหนดไว้ ORDER BY date, idไลค์:

คำอธิบายโดยละเอียด (ตอบคำถามที่คล้ายกันมาก):

หมายเหตุโดยเฉพาะ:

  • ในกรณีที่เกี่ยวข้องlag()ด้วยพารามิเตอร์ 3 ตัวอาจจำเป็นต่อการครอบคลุมกรณีมุมของแถวแรก (หรือสุดท้าย) อย่างหรูหรา (พารามิเตอร์ที่ 3 ถูกใช้เป็นค่าเริ่มต้นหากไม่มีแถวก่อนหน้า (ถัดไป)

    lag(id_type, 1, id_type) OVER ()

    เนื่องจากเราสนใจเพียงการเปลี่ยนแปลงที่แท้จริงของid_type( TRUE) จึงไม่สำคัญในกรณีนี้โดยเฉพาะ NULLและทั้งสองไม่นับเป็นFALSEstep

  • count(step OR NULL) OVER (ORDER BY date)เป็นไวยากรณ์ที่สั้นที่สุดที่ทำงานใน Postgres 9.3 หรือเก่ากว่า count()นับเฉพาะค่าที่ไม่ใช่ค่าว่าง ...

    ใน Postgres สมัยใหม่ตัวทำความสะอาดไวยากรณ์ที่เทียบเท่าจะเป็น:

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

    รายละเอียด:

2. ลบสองฟังก์ชั่นหน้าต่างหนึ่งแบบสอบถามย่อย

คล้ายกับแนวคิดของ Erik ที่มีการแก้ไข:

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

หากdateมีการกำหนดไว้UNIQUEเช่นเดียวกับที่ฉันพูดถึงข้างต้น (คุณไม่เคยชี้แจง) dense_rank()จะไม่มีจุดหมายเนื่องจากผลลัพธ์จะเหมือนกันสำหรับrow_number()และหลังมีราคาถูกกว่ามาก

หากdateจะไม่กำหนดUNIQUE(และเราไม่ทราบว่ารายการที่ซ้ำกันเท่านั้นอยู่บน(date, id_type)) ทั้งหมดของคำสั่งเหล่านี้จะไม่มีจุดหมายเนื่องจากผลที่ได้คือพล

นอกจากนี้แบบสอบถามย่อยมักจะถูกกว่า CTE ใน Postgres ใช้เพียง CTEs เมื่อคุณต้องการให้พวกเขา

คำตอบที่เกี่ยวข้องพร้อมคำอธิบายเพิ่มเติม:

ในกรณีที่เกี่ยวข้องซึ่งเรามีหมายเลขที่ทำงานอยู่แล้วในตารางเราสามารถทำได้ด้วยฟังก์ชั่นหน้าต่างเดียว:

3. ประสิทธิภาพสูงสุดพร้อมฟังก์ชั่น plpgsql

เนื่องจากคำถามนี้ได้รับความนิยมอย่างไม่คาดคิดฉันจะเพิ่มโซลูชันอื่นเพื่อแสดงประสิทธิภาพสูงสุด

SQL มีเครื่องมือที่ซับซ้อนมากมายในการสร้างโซลูชันที่มีไวยากรณ์สั้นและสง่างาม แต่ภาษาที่ประกาศได้มีข้อ จำกัด สำหรับข้อกำหนดที่ซับซ้อนมากขึ้นซึ่งเกี่ยวข้องกับองค์ประกอบของขั้นตอน

ฝั่งเซิร์ฟเวอร์ฟังก์ชั่นขั้นตอนจะเร็วกว่านี้อะไรโพสต์เพื่อให้ห่างไกลเพราะเพียงต้องการสแกนลำดับเดียวมากกว่าตารางและดำเนินการเรียงลำดับเดียว หากมีการติดตั้งดัชนีที่เหมาะสมแม้เพียงแค่การสแกนดัชนีอย่างเดียวเท่านั้น

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;

โทร:

SELECT * FROM f_tmp_groups();

ทดสอบกับ:

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

คุณสามารถทำให้ฟังก์ชั่นทั่วไปกับประเภท polymorphic และผ่านประเภทตารางและชื่อคอลัมน์ รายละเอียด:

หากคุณไม่ต้องการหรือไม่สามารถใช้งานฟังก์ชั่นนี้ได้คุณจะต้องจ่ายเงินเพื่อสร้างฟังก์ชั่นชั่วคราวทันที ค่าใช้จ่ายไม่กี่มิลลิวินาที


dbfiddleสำหรับ Postgres 9.6 การเปรียบเทียบประสิทธิภาพของทั้งสามอาคารในกรณีทดสอบของ Jack ได้รับการแก้ไข

dbfiddleสำหรับ Postgres 8.4ซึ่งความแตกต่างด้านประสิทธิภาพนั้นยิ่งใหญ่กว่า


อ่านสองสามครั้ง - ยังไม่แน่ใจในสิ่งที่คุณกำลังพูดถึงด้วยการโต้แย้งสามครั้งหรือเมื่อคุณต้องใช้count(x or null)หรือแม้แต่สิ่งที่กำลังทำอยู่ บางทีคุณอาจจะสามารถแสดงตัวอย่างบางส่วนที่มันจะถูกต้องเพราะมันไม่จำเป็นต้องที่นี่ และอะไรคือสิ่งที่สำคัญที่จะต้องครอบคลุมถึงกรณีมุมเหล่านั้น BTW ฉันเปลี่ยน downvote เป็น upvote เพียงแค่สำหรับตัวอย่าง pl / pgsql มันเจ๋งจริงๆ (แต่โดยทั่วไปแล้วฉันไม่เห็นด้วยกับคำตอบที่สรุปคำตอบอื่น ๆ หรือปกปิดมุม - แม้ว่าฉันเกลียดที่จะบอกว่านี่เป็นกรณีมุมเพราะฉันไม่เข้าใจ)
Evan Carroll

ฉันจะนำคำถามเหล่านี้มาแยกเป็นสองคำถามเพราะฉันแน่ใจว่าฉันไม่ใช่คนเดียวที่สงสัยว่าcount(x or null)จะทำอะไร ฉันยินดีที่จะถามทั้งสองคำถามหากคุณต้องการ
Evan Carroll


7

คุณสามารถทำสิ่งนี้เป็นการลบROW_NUMBER()การดำเนินการอย่างง่าย ๆ(หรือถ้าวันที่ของคุณไม่ซ้ำกัน แต่ก็ยังไม่ซ้ำกันid_typeคุณสามารถใช้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
;

ดูงานนี้ที่ DB Fiddle (หรือดูรุ่น DENSE_RANK )

ผลลัพธ์:

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

เหตุผลคุณคิดว่านี่เป็น a ง่ายๆDENSE_RANK()ด้วยPREORDER BYนั่นคือคุณต้องการรายการDENSE_RANKทั้งหมดที่อยู่ในอันดับเดียวกันและคุณต้องการให้เรียงลำดับตามวันที่คุณต้องจัดการกับปัญหาที่น่ารำคาญของความจริงที่ว่า ในการเปลี่ยนแปลงในแต่ละวันDENSE_RANKจะเพิ่มขึ้น คุณทำได้โดยใช้การแสดงออกตามที่ฉันแสดงให้คุณเห็นข้างต้น ลองนึกภาพว่าคุณมีไวยากรณ์นี้หรือไม่: DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)โดยที่PREORDERจะแยกออกจากการคำนวณอันดับและORDER BYนับเฉพาะ

โปรดทราบว่าเป็นสิ่งสำคัญสำหรับGROUP BYทั้งSeqคอลัมน์ที่สร้างขึ้นและid_typeคอลัมน์ Seqจะไม่ซ้ำด้วยตัวเองอาจมีการทับซ้อน - id_typeคุณต้องยังกลุ่มตาม

สำหรับการอ่านเพิ่มเติมในหัวข้อนี้:

ลิงก์แรกนั้นให้รหัสบางอย่างที่คุณสามารถใช้ได้หากคุณต้องการให้วันที่เริ่มต้นหรือวันที่สิ้นสุดเหมือนกับวันที่สิ้นสุด / เริ่มต้นของงวดก่อนหน้าหรือถัดไป (ดังนั้นจึงไม่มีช่องว่าง) รุ่นอื่น ๆ ที่สามารถช่วยคุณในการค้นหา แม้ว่าพวกเขาจะต้องถูกแปลจากไวยากรณ์ SQL Server ...


6

บน Postgres 8.4 คุณสามารถใช้ฟังก์ชันRECURSIVE

พวกเขาทำมันได้อย่างไร

ฟังก์ชันเรียกซ้ำเพิ่มระดับให้กับ id_type ที่แตกต่างกันโดยเลือกวันที่หนึ่งต่อหนึ่งตามลำดับจากมากไปน้อย

       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

จากนั้นใช้ MAX (วันที่), MIN (วันที่) จัดกลุ่มตามระดับ id_type เพื่อรับผลลัพธ์ที่ต้องการ

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

ตรวจสอบได้ที่: http://rextester.com/WCOYFP6623


5

นี่คือวิธีการอื่นซึ่งคล้ายกับ Evan และ Erwin's โดยใช้ LAG เพื่อกำหนดเกาะต่างๆ มันแตกต่างจากโซลูชันเหล่านั้นโดยใช้การซ้อนเพียงระดับเดียวเท่านั้นไม่มีการจัดกลุ่มและฟังก์ชั่นหน้าต่างอื่น ๆ อีกมาก:

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คอลัมน์คำนวณในเครื่องหมาย SELECT ซ้อนกันจุดเริ่มต้นของแต่ละเกาะ นอกจากนี้ SELECT แบบซ้อนจะเปิดเผยวันที่ก่อนหน้าของแต่ละแถวและวันที่สุดท้ายของชุดข้อมูล

สำหรับแถวที่เป็นจุดเริ่มต้นของหมู่เกาะนั้น ๆ วันที่ก่อนหน้าอย่างมีประสิทธิภาพคือวันที่สิ้นสุดของเกาะก่อนหน้า นั่นคือสิ่งที่ SELECT หลักใช้เป็น มันหยิบเฉพาะแถวที่ตรงกับis_start = 1สภาพและสำหรับแถวกลับมาก็แสดงให้เห็นแต่ละแถวของตัวเองdateเป็นbeginแถวต่อไปเป็นprev_date endเนื่องจากแถวสุดท้ายไม่มีแถวต่อไปนี้ให้LEAD(prev_date)ส่งคืนค่า null สำหรับฟังก์ชัน COALESCE ที่ใช้แทนวันที่สุดท้ายของชุดข้อมูล

คุณสามารถเล่นกับการแก้ปัญหานี้ที่ dbfiddle

เมื่อแนะนำคอลัมน์เพิ่มเติมเพื่อระบุหมู่เกาะคุณอาจต้องการแนะนำพาร์ทิชันย่อยตาม subclause ไปยังส่วนคำสั่ง OVER ของแต่ละฟังก์ชันของหน้าต่าง ตัวอย่างเช่นหากคุณต้องการตรวจจับหมู่เกาะภายในกลุ่มที่กำหนดโดย a parent_idข้อความค้นหาด้านบนอาจจะต้องมีลักษณะดังนี้:

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
;

และถ้าคุณตัดสินใจที่จะใช้วิธีแก้ปัญหาของเออร์วินหรืออีวานฉันเชื่อว่าการเปลี่ยนแปลงที่คล้ายกันจะต้องถูกเพิ่มเข้าไปด้วย


5

เพิ่มเติมจากนักวิชาการที่น่าสนใจมากกว่าที่จะเป็นวิธีการแก้ปัญหาในทางปฏิบัติคุณยังสามารถบรรลุเป้าหมายนี้ด้วยการรวมที่ผู้ใช้กำหนด เช่นเดียวกับโซลูชันอื่น ๆ สิ่งนี้จะใช้ได้แม้ใน Postgres 8.4 แต่อย่างที่คนอื่น ๆ ได้แสดงความคิดเห็นโปรดอัปเกรดถ้าคุณทำได้

การรวมการจัดการnullราวกับว่ามันแตกต่างกันfoo_typeดังนั้นการวิ่งของโมฆะจะได้รับเหมือนกันgrp- ที่อาจหรือไม่อาจเป็นสิ่งที่คุณต้องการ

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;
start_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 ที่นี่


4

สิ่งนี้สามารถทำได้ด้วยRECURSIVE CTEการผ่าน "เวลาเริ่มต้น" จากแถวหนึ่งไปยังแถวถัดไปและการเตรียมการพิเศษ (ความสะดวกสบาย) บางอย่าง

แบบสอบถามนี้ส่งคืนผลลัพธ์ที่คุณต้องการ:

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

หลังจากการเตรียม ... ส่วนที่เกิดซ้ำ

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

คุณสามารถตรวจสอบได้ที่http://rextester.com/POYM83542

วิธีนี้ใช้ไม่ได้ผล สำหรับตารางแถว 8_641 จะใช้เวลา 7 วินาทีสำหรับตารางสองเท่าของขนาดนั้นจะใช้เวลา 28 วินาที อีกสองสามตัวอย่างแสดงเวลาดำเนินการที่ดูเหมือน O (n ^ 2)

วิธีการของ Evan Carrol ใช้เวลาน้อยกว่า 1 วินาที (เช่น: ลองเลย!) และดูเหมือน O (n) ข้อความค้นหาแบบเรียกซ้ำไม่มีประสิทธิภาพอย่างแน่นอนและควรได้รับการพิจารณาเป็นทางเลือกสุดท้าย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.