Phân nhóm hoặc cửa sổ


13

Tôi có một tình huống tôi nghĩ có thể được giải quyết bằng chức năng cửa sổ nhưng tôi không chắc chắn.

Hãy tưởng tượng bảng sau

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

Tôi muốn có một nhóm mới ở mỗi thay đổi trên cột id_type. Nhóm thứ nhất của EG từ 7:19:21 đến 7:19:25, bắt đầu và kết thúc thứ 2 lúc 7:19:26, v.v.
Sau khi nó hoạt động, tôi muốn bao gồm nhiều tiêu chí hơn để xác định các nhóm.

Tại thời điểm này, sử dụng truy vấn bên dưới ...

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;

Tôi nhận được kết quả sau:

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

Trong khi tôi muốn:

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

Sau khi tôi giải quyết bước đầu tiên này, tôi sẽ thêm nhiều cột để sử dụng làm quy tắc để phá vỡ các nhóm và những cột khác sẽ không thể thực hiện được.

Phiên bản Postgres: 8.4 (Chúng tôi có Postgres với Postgis, vì vậy không dễ để nâng cấp. Chức năng Postgis thay đổi tên và có những vấn đề khác, nhưng hy vọng chúng tôi đã viết lại mọi thứ và phiên bản mới sẽ sử dụng phiên bản mới hơn 9.X với postgis 2.x)


Câu trả lời:


4

Đối với một vài điểm,

  • Đừng gọi một bảng không tạm thời tmpchỉ gây nhầm lẫn.
  • Không sử dụng văn bản cho dấu thời gian (bạn đang làm điều đó trong ví dụ của bạn, chúng tôi có thể biết vì dấu thời gian không bị cắt ngắn và có .0)
  • Đừng gọi một lĩnh vực có thời gian trong đó date. Nếu nó có ngày và thời gian, đó là dấu thời gian (và lưu trữ dưới dạng một)

Tốt hơn để sử dụng một chức năng cửa sổ ..

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

Đầu ra

 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)

Giải thích

Đầu tiên chúng ta cần đặt lại .. Chúng ta tạo chúng với 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)

Sau đó, chúng tôi đếm để có được các nhóm.

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)

Sau đó, chúng tôi quấn trong một subselect GROUP BYORDERvà chọn max min (range)

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

16

1. Các chức năng của cửa sổ cộng với các truy vấn con

Đếm các bước để hình thành các nhóm, tương tự như ý tưởng của Evan , với các sửa đổi và sửa lỗi:

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

Giả định này liên quan đến cột là NOT NULL. Khác bạn cần phải làm nhiều hơn nữa.

Ngoài ra, giả sử đã dateđược xác định UNIQUE, bạn cần thêm một bộ bẻ khóa vào các ORDER BYmệnh đề để có kết quả xác định. Giống như : ORDER BY date, id.

Giải thích chi tiết (trả lời cho câu hỏi rất giống nhau):

Đặc biệt lưu ý:

  • Trong các trường hợp liên quan, lag()với 3 tham số có thể cần thiết để bao phủ trường hợp góc của hàng đầu tiên (hoặc cuối cùng) một cách thanh lịch. (Thông số thứ 3 được sử dụng làm mặc định nếu không có hàng trước (tiếp theo).

    lag(id_type, 1, id_type) OVER ()

    Vì chúng tôi chỉ quan tâm đến một thay đổi thực sự của id_type( TRUE), nên không có vấn đề gì trong trường hợp cụ thể này. NULLFALSEcả hai không được tính là step.

  • count(step OR NULL) OVER (ORDER BY date)là cú pháp ngắn nhất cũng hoạt động trong Postgres 9.3 trở lên. count()chỉ tính các giá trị khác không ...

    Trong Postgres hiện đại, cú pháp tương đương sạch hơn sẽ là:

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

    Chi tiết:

2. Trừ hai hàm cửa sổ, một truy vấn con

Tương tự như ý tưởng của Erik với các sửa đổi:

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

Nếu dateđược xác định UNIQUE, như tôi đã đề cập ở trên (bạn không bao giờ làm rõ), dense_rank()sẽ là vô nghĩa, vì kết quả là giống như cho row_number()và sau này là rẻ hơn đáng kể.

Nếu datekhông định nghĩa UNIQUE(và chúng ta không biết rằng các bản sao chỉ là trên (date, id_type)), tất cả các truy vấn này là vô nghĩa, vì kết quả là tùy ý.

Ngoài ra, một truy vấn con thường rẻ hơn CTE trong Postgres. Chỉ sử dụng CTE khi bạn cần chúng.

Câu trả lời liên quan với nhiều lời giải thích:

Trong các trường hợp liên quan khi chúng tôi đã có một số đang chạy trong bảng, chúng tôi có thể thực hiện với một chức năng cửa sổ duy nhất:

3. Hiệu suất hàng đầu với chức năng plpgsql

Vì câu hỏi này đã trở nên phổ biến bất ngờ, tôi sẽ thêm một giải pháp khác để chứng minh hiệu suất hàng đầu.

SQL có nhiều công cụ tinh vi để tạo ra các giải pháp với cú pháp ngắn và thanh lịch. Nhưng một ngôn ngữ khai báo có giới hạn của nó đối với các yêu cầu phức tạp hơn liên quan đến các yếu tố thủ tục.

Một chức năng thủ tục phía máy chủ là nhanh hơn cho bất cứ điều gì được đăng cho đến nay bởi vì nó chỉ cần một lần quét tuần tự duy nhất trên bảng và một hoạt động sắp xếp duy nhất . Nếu có sẵn một chỉ mục phù hợp, thậm chí chỉ quét một lần duy nhất.

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;

Gọi:

SELECT * FROM f_tmp_groups();

Kiểm tra với:

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

Bạn có thể làm cho hàm chung với các kiểu đa hình và vượt qua tên bảng và kiểu cột. Chi tiết:

Nếu bạn không muốn hoặc không thể duy trì một chức năng cho việc này, nó thậm chí sẽ trả tiền để tạo một chức năng tạm thời một cách nhanh chóng. Chi phí một vài ms.


dbfiddle cho Postgres 9.6, so sánh hiệu suất của cả ba. Xây dựng trêntrường hợp thử nghiệm của Jack, đã được sửa đổi.

dbfiddle cho Postgres 8.4, trong đó sự khác biệt về hiệu năng thậm chí còn lớn hơn.


Đọc điều này một vài lần - vẫn không chắc chắn về những gì bạn đang nói về độ trễ ba đối số hoặc khi bạn phải sử dụng count(x or null)hoặc thậm chí những gì nó đang làm ở đó. Có lẽ bạn có thể hiển thị một số mẫu, nơi nó được yêu cầu, bởi vì nó không cần thiết ở đây. Và, những gì sẽ quan trọng yêu cầu để bao gồm các trường hợp góc. BTW, tôi đã thay đổi downvote của mình thành upvote chỉ cho ví dụ pl / pssql. Điều đó thật tuyệt. (Nhưng, nói chung tôi chống lại các câu trả lời tóm tắt các câu trả lời khác hoặc bao gồm các trường hợp góc - mặc dù tôi ghét phải nói rằng đây là trường hợp góc vì tôi không hiểu nó).
Evan Carroll

Tôi sẽ đặt chúng vào hai câu hỏi tự trả lời riêng biệt bởi vì tôi chắc chắn tôi không phải là người duy nhất tự hỏi điều gì count(x or null). Tôi sẽ rất vui khi được hỏi cả hai câu hỏi nếu bạn muốn.
Evan Carroll


7

Bạn có thể thực hiện việc này như một phép trừ đơn giản cho các ROW_NUMBER()thao tác (hoặc nếu ngày của bạn không phải là duy nhất, mặc dù vẫn là duy nhất id_type, thì bạn có thể sử dụng DENSE_RANK()thay thế, mặc dù đó sẽ là một truy vấn đắt tiền hơn):

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
;

Xem tác phẩm này tại DB Fiddle (hoặc xem phiên bản DENSE_RANK )

Kết quả:

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

Về mặt logic, bạn có thể nghĩ điều này đơn giản DENSE_RANK()với một PREORDER BY, nghĩa là, bạn muốn DENSE_RANKtất cả các mục được xếp cùng nhau, và bạn muốn chúng được sắp xếp theo ngày, bạn chỉ phải đối phó với vấn đề rắc rối của thực tế là tại mỗi thay đổi trong ngày, DENSE_RANKsẽ tăng lên. Bạn làm điều đó bằng cách sử dụng biểu thức như tôi đã chỉ ra ở trên. Hãy tưởng tượng nếu bạn có cú pháp này: DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)trong đó PREORDERloại trừ khỏi tính toán xếp hạng và chỉ ORDER BYtính được tính.

Lưu ý rằng điều quan trọng đối với GROUP BYcả Seqcột được tạo cũng như id_typecột. SeqKHÔNG phải là duy nhất của chính nó, có thể có sự chồng chéo - bạn cũng phải nhóm theo id_type.

Để đọc thêm về chủ đề này:

Liên kết đầu tiên đó cung cấp cho bạn một số mã bạn có thể sử dụng nếu bạn muốn ngày bắt đầu hoặc ngày kết thúc giống với ngày kết thúc / ngày bắt đầu của giai đoạn trước hoặc tiếp theo (vì vậy không có khoảng trống). Cộng với các phiên bản khác có thể hỗ trợ bạn trong truy vấn của bạn. Mặc dù chúng phải được dịch từ cú pháp SQL Server ...


6

Trên Postgres 8.4, bạn có thể sử dụng chức năng RECURSIVE .

Họ làm nó như thế nào

Hàm đệ quy thêm một mức cho mỗi id_type khác nhau, bằng cách chọn từng ngày một theo thứ tự giảm dần.

       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

Sau đó sử dụng nhóm MAX (ngày), MIN (ngày) theo cấp độ, id_type để có kết quả mong muố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    |
+---------------------+---------------------+---------+

Kiểm tra nó: http://rextester.com/WCOYFP6623


5

Đây là một phương pháp khác, tương tự như của Evan và Erwin ở chỗ nó sử dụng LAG để xác định các đảo. Nó khác với các giải pháp đó ở chỗ nó chỉ sử dụng một cấp độ lồng nhau, không phân nhóm và nhiều chức năng cửa sổ hơn:

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
;

Các is_startcột tính toán trong dấu ngoặc lồng nhau CHỌN đầu mỗi đảo. Ngoài ra, CHỌN lồng nhau hiển thị ngày trước của mỗi hàng và ngày cuối cùng của bộ dữ liệu.

Đối với các hàng là điểm bắt đầu của các đảo tương ứng, ngày trước đó thực sự là ngày kết thúc của đảo trước đó. Đó là những gì CHỌN chính sử dụng nó như. Nó chọn chỉ có các hàng phù hợp với is_start = 1điều kiện, và cho mỗi hàng trả lại cho thấy sự của hàng riêng datenhư beginvà hàng sau là prev_datenhư end. Vì hàng cuối cùng không có hàng tiếp theo, LEAD(prev_date)trả về null cho hàng đó, hàm COALESCE thay thế ngày cuối cùng của tập dữ liệu.

Bạn có thể chơi với giải pháp này tại dbfiddle .

Khi giới thiệu các cột bổ sung xác định các đảo, có lẽ bạn sẽ muốn giới thiệu một phần THAM GIA BỞI cho mệnh đề QUÁN của mỗi chức năng cửa sổ. Ví dụ: nếu bạn muốn phát hiện các đảo trong các nhóm được xác định bởi a parent_id, truy vấn trên có thể sẽ cần phải trông như thế này:

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
;

Và nếu bạn quyết định sử dụng giải pháp của Erwin hoặc Evan, tôi tin rằng một sự thay đổi tương tự cũng sẽ cần được thêm vào nó.


5

Nhiều hơn lợi ích học tập hơn là một giải pháp thực tế, bạn cũng có thể đạt được điều này với tổng hợp do người dùng xác định . Giống như các giải pháp khác, điều này sẽ hoạt động ngay cả trên Postgres 8.4, nhưng như những người khác đã nhận xét, vui lòng nâng cấp nếu bạn có thể.

Các tổng hợp xử lý nullnhư thể nó là một khác biệt foo_type, do đó các chuỗi null sẽ được cung cấp giống nhau grp- đó có thể hoặc không thể là những gì bạn muốn.

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;
bắt đầu | kết thúc | 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 ở đây


4

Điều này có thể được thực hiện RECURSIVE CTEđể vượt qua "thời gian bắt đầu" từ hàng này sang hàng tiếp theo và một số chuẩn bị bổ sung (tiện lợi).

Truy vấn này trả về kết quả bạn muốn:

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

sau khi chuẩn bị ... phần đệ quy

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

Bạn có thể kiểm tra điều này tại http://rextester.com/POYM83542

Phương pháp này không có quy mô tốt. Đối với một bảng hàng 8_641, phải mất 7 giây, đối với một bảng có kích thước gấp đôi, phải mất 28 giây. Một vài mẫu nữa hiển thị thời gian thực hiện trông giống như O (n ^ 2).

Phương pháp của Evan Carrol mất ít hơn 1 giây (nghĩa là: đi cho nó!) Và trông giống như O (n). Các truy vấn đệ quy là hoàn toàn không hiệu quả, và nên được coi là phương sách cuối cùng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.