2013-03-21 3 views
3

timespan을 나타내는 타임 스탬프 쌍이 포함 된 테이블이 있습니다. 이 행은 사용자 ID별로 범위가 지정되며 각 사용자는 하나 이상의 행을 연관시킬 수 있습니다.PostgreSQL SELECT는 여러 행에 걸쳐 일치해야합니다.

이 데이터는 주중에 사용자를 사용할 수있는 시간을 나타내는 추상 "가용성"형식으로 생성됩니다. 일련의 시간 범위를 쿼리로 입력하고 모든 행이 일치하는 모든 사용자 ID를 반환해야합니다.

CREATE TABLE "public"."availability" (
    "id" int4 NOT NULL, 
    "user_id" int4, 
    "starts_at" timestamp(6), 
    "ends_at" timestamp(6), 
    PRIMARY KEY ("id") 
) WITH (OIDS=FALSE) 

이 데이터 :이 테이블을 감안할 때

User #1 is available Mon-Tue between 08:00 and 17:00 

+----+---------+---------------------+---------------------+ 
| id | user_id | starts_at   | ends_at    | 
+----+---------+---------------------+---------------------+ 
| 1 | 1  | 2013-03-18 08:00:00 | 2013-03-18 17:00:00 | 
+----+---------+---------------------+---------------------+ 
| 2 | 1  | 2013-03-19 08:00:00 | 2013-03-19 17:00:00 | 
+----+---------+---------------------+---------------------+ 

User #2 is available Sun-Sat all day 

+----+---------+---------------------+---------------------+ 
| 3 | 2  | 2013-03-17 00:00:00 | 2013-03-23 23:59:59 | 
+----+---------+---------------------+---------------------+ 

User #3 is available Wed between 06:00 and 18:00 

+----+---------+---------------------+---------------------+ 
| 4 | 3  | 2013-03-20 06:00:00 | 2013-03-20 18:00:00 | 
+----+---------+---------------------+---------------------+ 

내가 할 수 주어진 타임 스탬프의 사용할 수 있습니다 쉽게 선택 사용자 :

SELECT * FROM "public"."availability" 
    WHERE ('2013-03-19 08:35:00' BETWEEN starts_at AND ends_at 
    AND '2013-03-19 18:25:00' BETWEEN starts_at AND ends_at) 
    OR ('2013-03-20 12:00:00' BETWEEN starts_at AND ends_at 
    AND '2013-03-20 18:00:00' BETWEEN starts_at AND ends_at); 

+----+---------+---------------------+---------------------+ 
| id | user_id | starts_at   | ends_at    | 
+----+---------+---------------------+---------------------+ 
| 3 | 2  | 2013-03-17 00:00:00 | 2013-03-23 23:59:59 | 
+----+---------+---------------------+---------------------+ 
| 4 | 3  | 2013-03-20 06:00:00 | 2013-03-20 18:00:00 | 
+----+---------+---------------------+---------------------+ 

하지만 실제로 필요한 것은 timespans를 여러 번 쿼리하고 모두과 일치하는 user_id만을 사용하십시오.

검색어 : 2013-03-17 10:00:00-2013-03-17 16:00:002013-03-23 10:00:00 - 2013-03-23 16:00:00은 반환해야합니다 :

+----+---------+---------------------+---------------------+ 
| id | user_id | starts_at   | ends_at    | 
+----+---------+---------------------+---------------------+ 
| 3 | 2  | 2013-03-17 00:00:00 | 2013-03-23 23:59:59 | 
+----+---------+---------------------+---------------------+ 

검색어 : 2013-03-18 09:00:00-2013-03-18 16:00:002013-03-19 08:00:00 - 2013-03-19 15:45:00은 반환해야합니다 :

+----+---------+---------------------+---------------------+ 
| id | user_id | starts_at   | ends_at    | 
+----+---------+---------------------+---------------------+ 
| 1 | 1  | 2013-03-18 08:00:00 | 2013-03-18 17:00:00 | 
+----+---------+---------------------+---------------------+ 
| 2 | 1  | 2013-03-19 08:00:00 | 2013-03-19 17:00:00 | 
+----+---------+---------------------+---------------------+ 
| 3 | 2  | 2013-03-17 00:00:00 | 2013-03-23 23:59:59 | 
+----+---------+---------------------+---------------------+ 

검색어 : 2013-03-18 07:00:00-2013-03-18 18:00:00 아무것도 반환해야 .

+1

SQLFiddle : http://sqlfiddle.com/#!12/b0fcf/4 –

+0

@CraigRinger 감사합니다! 나는 SQLfiddle을 모르고 있었고, 유용했다. –

+0

질문을 수정 해 주셔서 감사합니다. 나는 지금 당장 무관심한 대답을 없애고 다른 사람들에게 내가 지금 시간이 없어서 그 일을 맡길 것이다. –

답변

2

SQL Fiddle

이 일치하는 기간의 수에 0 또는 1

select a.* 
from 
    availability a 
    inner join 
    (
     select 
      user_id, 
      sum (
       ('2013-03-18 09:00:00' between starts_at and ends_at 
       and 
       '2013-03-18 16:00:00' between starts_at and ends_at 
       )::integer 
       + 
       ('2013-03-19 08:00:00' between starts_at and ends_at 
       and 
       '2013-03-19 15:45:00' between starts_at and ends_at 
       )::integer 
      ) period 
     from availability 
     group by user_id 
    ) s on a.user_id = s.user_id 
where period >= 2 

변경 등의 정수로 부울 캐스팅 where 조건을 이용

SQLFiddle example.

+0

매우 영리합니다 ! 감사. –

2

이와 같은 응용 프로그램의 경우 PostgreSQL 버전 9.2 이상을 사용하는 경우 range type을 사용해보십시오. 여기서 생성, 로딩, 데이터를 표시하는 예입니다

CREATE TABLE availability (
    id  int4 NOT NULL, 
    user_id int4, 
    avail tstzrange, 
    PRIMARY KEY (id) 
); 
INSERT INTO availability VALUES 
    (1, 1, '[2013-03-18 08:00:00, 2013-03-18 17:00:00)'), 
    (2, 1, '[2013-03-19 08:00:00, 2013-03-19 17:00:00)'), 
    (3, 2, '[2013-03-17 00:00:00, 2013-03-23 24:00:00)'), 
    (4, 3, '[2013-03-20 06:00:00, 2013-03-20 18:00:00)'); 
SELECT * FROM availability ; 
 
id | user_id |      avail       
----+---------+----------------------------------------------------- 
    1 |  1 | ["2013-03-18 08:00:00-05","2013-03-18 17:00:00-05") 
    2 |  1 | ["2013-03-19 08:00:00-05","2013-03-19 17:00:00-05") 
    3 |  2 | ["2013-03-17 00:00:00-05","2013-03-24 00:00:00-05") 
    4 |  3 | ["2013-03-20 06:00:00-05","2013-03-20 18:00:00-05") 
(4 rows) 

그런 다음 다양한 사업자와 함께 조회 할 수 있습니다.당신이 포함 된 모든 가용성 범위를 원하는 경우 지정된 쿼리의 범위는 :

SELECT * FROM availability 
    WHERE avail @> '[2013-03-19 08:35:00, 2013-03-19 18:25:00)' 
    OR avail @> '[2013-03-20 12:00:00, 2013-03-20 18:00:00)'; 

나 :

SELECT * FROM availability 
    WHERE avail @> ANY 
      (ARRAY ['[2013-03-19 08:35:00, 2013-03-19 18:25:00)'::tstzrange, 
        '[2013-03-20 12:00:00, 2013-03-20 18:00:00)'::tstzrange]); 
 
id | user_id |      avail       
----+---------+----------------------------------------------------- 
    3 |  2 | ["2013-03-17 00:00:00-05","2013-03-24 00:00:00-05") 
    4 |  3 | ["2013-03-20 06:00:00-05","2013-03-20 18:00:00-05") 
(2 rows) 

당신은 지정의 모든를 포함하는 모든 가용성 범위를 원하는 경우 단일 범위의 검색어 범위 :

SELECT * FROM availability 
    WHERE avail @> '[2013-03-17 10:00:00, 2013-03-17 16:00:00)' 
    AND avail @> '[2013-03-23 10:00:00, 2013-03-23 16:00:00)'; 

나 :

SELECT * FROM availability 
    WHERE avail @> ALL 
      (ARRAY ['[2013-03-17 10:00:00, 2013-03-17 16:00:00)'::tstzrange, 
        '[2013-03-23 10:00:00, 2013-03-23 16:00:00)'::tstzrange]); 
 
id | user_id |      avail       
----+---------+----------------------------------------------------- 
    3 |  2 | ["2013-03-17 00:00:00-05","2013-03-24 00:00:00-05") 
(1 row) 

당신이 지정된 쿼리의범위 만 특정 쿼리의 모든을 포함 가용성 범위와 사용자가 포함 된 모든 가용성 범위를 원하는 경우

범위 :

또는 범위를 사용하는 lodoaldo 네토의 쿼리) :

CREATE INDEX availability_avail ON availability USING gist (avail); 

참고 :

  • 내가 그만 둔 당신은이 같은 큰 테이블에 매우 빠르게 같은 검색을 할 수있는 인덱스를 생성 할 수 있습니다

    SELECT a.* 
        FROM availability a 
        JOIN (
         SELECT 
          user_id, 
          sum(('[2013-03-18 09:00:00, 2013-03-18 16:00:00)'::tstzrange 
            <@ avail)::integer 
           + 
           ('[2013-03-19 08:00:00, 2013-03-19 15:45:00)'::tstzrange 
            <@ avail)::integer 
           ) period 
          FROM availability 
          GROUP BY user_id 
         ) s ON a.user_id = s.user_id 
        WHERE period >= 2; 
    

    스키마 및 가독성을위한 인용 부호.

  • 하나의 데이터 페이지를 직접 읽음으로써 모든 데이터를 더 빨리 사용할 수 있기 때문에 색인은 4 개의 행과 함께 사용되지 않을 수 있습니다. 큰 테이블을 사용하면 큰 차이를 만들 수 있습니다.
  • 기본 (베어) TIMESTAMP 일광 절약 시간이 끝날 때마다 시계가 매년 뒤로 이동하기 때문에 범위를 TIMESTAMP WITH TIME ZONE으로 사용했습니다. 잠시 시간을 잡으려면 TIMESTAMP WITH TIME ZONE (줄여서 timestamptz)을 사용하십시오.
  • 직접 사용할 때 리터럴을 명시 적으로 캐스팅 할 필요는 없습니다. 쿼리의 ANY 또는 ALL 형식을 사용할 때 명시 적 형 변환이 필요합니다. 둥근 괄호는 그 범위 제외 인접 평균 시간 동안 범위에
  • 대괄호의 범위 인접한 타임 포함하는 것을 의미한다. 시간 소인은 일반적으로 [)을 사용하여 지정되므로 주어진 시간으로 끝나는 범위와 같은 시간으로 시작하는 다른 범위는 이 중복되는이 아닌 인접한 것으로 간주됩니다.
  • '24:00:00' 한 날짜는 '00:00:00' 다음 날입니다.
  • 위의 두 가지 점을 사용하면 자정에 끝나는 타임 스탬프를 쉽게 지정할 수 있습니다. "잃어버린 두 번째"또는 다른 이상한 위험이 없습니다.
+0

범위 데이터 유형이이 문제에 적합하기 때문에 _more correct_ 대답 일 가능성이 높습니다. 우리는 9.2 로의 업그레이드 작업을 진행하고 있습니다. 따라서 이것이 결국 제가 끝낼 방법입니다. –

관련 문제