그룹화 또는 창


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 열에서 변경 될 때마다 새 그룹을 만들고 싶습니다. EG 1 차 그룹 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 (Postgis를 사용하는 Postgres가 있으므로 업그레이드하기가 쉽지 않습니다. Postgis 함수는 이름을 변경하고 다른 문제가 있지만, 이미 모든 내용을 다시 작성하고 새 버전은 최신 버전 9.X를 사용합니다. postgis 2.x)


답변:


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)

그런 다음 우리는 부속 선택에 포장 GROUP BYORDER및 최소 최대 (범위)를 선택

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타이 브레이커를 추가해야 ORDER BY합니다. 처럼 : ORDER BY date, id.

자세한 설명 (매우 비슷한 질문에 대한 답변) :

특히 참고 사항 :

  • 관련된 경우 첫 번째 (또는 마지막) 행의 코너 케이스를 우아하게 처리하기 위해 lag()3 개의 매개 변수 가 필수적 일 수 있습니다. (이전 (다음) 행이없는 경우 세 번째 매개 변수가 기본값으로 사용됩니다.

    lag(id_type, 1, id_type) OVER ()

    우리 만 실제에 관심이 있기 때문에 변화id_type( TRUE),이 특별한 경우에 문제가되지 않습니다. NULL그리고 FALSE둘은 포함되지 않습니다 step.

  • 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)) 결과가 임의이기 때문에, 이러한 모든 쿼리는 무의미하다.

또한 하위 쿼리는 일반적으로 Postgres의 CTE보다 저렴합니다. 필요할 때만 CTE를 사용하십시오 .

자세한 설명과 관련된 답변 :

테이블에 이미 번호가있는 관련 사례에서는 단일 창 함수로 수행 할 수 있습니다.

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

함수를 다형성 유형으로 전달하고 테이블 유형 및 열 이름을 전달할 수 있습니다. 세부:

이를 위해 기능을 원치 않거나 지속시킬 수 없다면, 임시 기능을 즉시 생성하는 비용도 지불해야합니다. 몇 ms가 소요됩니다.


dbfiddle 포스트 그레스 9.6에 대한, 모든 three.Building에 대한 성능을 비교잭의 테스트 케이스, 수정했습니다.

성능 차이가 훨씬 큰Postgres 8.4 용 dbfiddle


이것을 몇 번 읽으십시오-여전히 세 가지 논쟁의 지연에 대해 이야기하고 있거나 언제 사용 count(x or null)해야하는지 또는 그것이 무엇을하고 있는지 확실하지 않습니다. 아마 당신이 몇 가지 샘플 보여줄 수 있다 는 여기에 필요하지 있기 때문에 필요합니다. 그리고 그러한 모퉁이 사건을 다루기위한 요구 사항은 무엇입니까? BTW, 나는 pl / pgsql 예제를 위해 downvote를 upvote로 변경했습니다. 정말 멋지다. (그러나 일반적으로 나는 다른 답변을 요약하거나 모퉁이 사건을 다루는 답변에 반대합니다-비록 이것이 이해하지 못하기 때문에 모퉁이 사건이라고 말하는 것이 싫습니다).
Evan Carroll

나는 그들이 무엇을 궁금해하는 유일한 사람이 아니라고 확신하기 때문에 두 개의 별도의 자체 답변 질문에 넣을 것입니다 count(x or null). 원한다면 두 가지 질문을 드리겠습니다.
Evan Carroll


7

ROW_NUMBER()작업을 간단한 빼기 작업 으로 수행 할 수 있습니다 (또는 날짜가 고유하지는 않지만 여전히 per 고유 하지만 더 비싼 쿼리 일지라도 대신 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

논리적으로, 당신은 간단하게 생각할 수 DENSE_RANK()A를 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

다음은 LAG를 사용하여 섬을 결정한다는 점에서 Evan 및 Erwin과 유사한 또 다른 방법입니다. 하나의 중첩 수준, 그룹화 및 상당히 많은 창 기능을 사용한다는 점에서 솔루션과 다릅니다.

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) COALESCE 함수가 데이터 세트의 마지막 날짜를 대체하는 널을 리턴합니다.

dbfiddle 에서이 솔루션 사용할 수 있습니다 .

섬을 식별하는 추가 열을 도입 할 때 각 창 함수의 OVER 절에 PARTITION BY 하위 절을 도입하려고 할 수 있습니다. 예를 들어에 의해 정의 된 그룹 내에서 섬을 감지 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
;

그리고 Erwin 또는 Evan의 솔루션을 사용하기로 결정한 경우 비슷한 변경 사항도 추가해야한다고 생각합니다.


5

실제 솔루션보다 학문적 관심이없는 사용자 정의 집계를 통해이를 달성 할 수도 있습니다 . 다른 솔루션과 마찬가지로 Postgres 8.4에서도 작동하지만 다른 사람들이 언급했듯이 가능하면 업그레이드하십시오.

집계는 null다른 것처럼 취급 foo_type하므로 null 실행에는 동일하게 제공 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;
begin_at | end_at | foo_type
: -------------------- | : -------------------- | ------- :
2017-01-10 07:19:21 | 2017-01-10 07:19:25 | 삼
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 | 삼
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 | 삼
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 | 삼
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)처럼 보이는 실행 시간을 보여줍니다.

에반 캐롤의 방법은 1 초보다 적게 걸리며 (즉, 가자!) O (n)처럼 보입니다. 재귀 쿼리는 절대적으로 비효율적이므로 최후의 수단으로 고려해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.