4

タイムスパンを表すタイムスタンプのペアを含むテーブルがあります。これらの行はユーザー ID によってスコープされ、各ユーザーは 1 つまたは複数の行を関連付けることができます。

このデータは、ユーザーが週にいつ利用できるかを表す抽象的な「利用可能性」フォームから生成されます。一連の時間範囲をクエリとして入力し、テーブル内のすべての行が一致するすべてのユーザー 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 |
+----+---------+---------------------+---------------------+

しかし、私が本当に必要としているのは、複数のタイムスパンを照会して、すべての条件にuser_id一致する のみを返すことができることです。

クエリ: 2013-03-17 10:00:00- 2013-03-17 16:00:00, 2013-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:00, 2013-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何も返さないはずです。

SQLFiddle の例

4

2 に答える 2

4

このようなアプリケーションの場合、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 | ユーザー ID | 役に立つ                        
-+---------------------+-------------------------------------------------- ------------------
  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行)

次に、さまざまな演算子を使用してクエリを実行できます。指定したクエリ範囲のいずれかを含むすべての可用性範囲が必要な場合:

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 | ユーザー ID | 役に立つ                        
-+---------------------+-------------------------------------------------- ------------------
  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行)

指定したすべてのクエリ範囲を 1 つの範囲に含むすべての可用性範囲が必要な場合:

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 | ユーザー ID | 役に立つ                        
-+---------------------+-------------------------------------------------- ------------------
  3 | 2 | ["2013-03-17 00:00:00-05","2013-03-24 00:00:00-05")
(1行)

指定されたクエリ範囲のいずれかを含むすべての可用性範囲が必要であるが、指定されたクエリ範囲すべてカバーする可用性範囲を持つユーザーのみが必要な場合:

WITH s(ts) AS
(
  VALUES
    ('[2013-03-18 09:00:00, 2013-03-18 16:00:00)'::tstzrange),
    ('[2013-03-19 08:00:00, 2013-03-19 15:45:00)'::tstzrange)
)
SELECT DISTINCT a1.*
  FROM s s1
  JOIN availability a1 ON a1.avail @> s1.ts
    AND NOT EXISTS
        (
          SELECT * FROM s s2
            WHERE NOT EXISTS
                  (
                    SELECT * FROM availability a2
                      WHERE a2.user_id = a1.user_id
                        AND a2.avail @> s2.ts
                  )
        );

または(Clodoaldo Netoのクエリを範囲を使用するように適応させる):

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;

次のような大きなテーブルでこのような検索を非常に高速にするインデックスを作成できます。

CREATE INDEX availability_avail ON availability USING gist (avail);

ノート:

  • 読みやすくするために、スキーマと引用符を省略しました。
  • 1 つのデータ ページを直接読み取ることで、すべてのデータをより迅速に利用できるようになるため、インデックスを 4 行で使用することはほとんどありません。大きなテーブルでは、大きな違いが生じる可能性があります。
  • 範囲を使用TIMESTAMP WITH TIME ZONEしたのは、デフォルト (ベア)TIMESTAMPでは、夏時間の終わりに時計が毎年後ろに移動するためです。ある瞬間をキャプチャするには、TIMESTAMP WITH TIME ZONE(timestamptz略して) を使用します。
  • 直接使用する場合、リテラルを明示的にキャストする必要はありません。ANYorALL形式のクエリを使用する場合は、明示的なキャストが必要です。
  • 範囲の角括弧は範囲に隣接する時間が含まれることを意味し、丸括弧は範囲が隣接する時間を除外することを意味します。タイムスタンプは一般に を使用して指定される[)ため、特定の時刻で終了する範囲と、同じ時刻で開始する別の範囲は、重複するのではなく隣接していると見なされます。
  • '24:00:00'ある日付と'00:00:00'次の日付は同じ瞬間です。
  • 前述の 2 つのポイントにより、午前 0 時に終了するタイムスタンプを簡単に指定できます。「ロストセコンド」やその他の異常のリスクはありません。
于 2013-03-21T21:21:02.877 に答える
3

SQL フィドル

これは、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一致する期間の数に条件を変更します。

于 2013-03-21T16:04:00.353 に答える