sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQLで重複する日付範囲を検索する

    現在受け入れられている回答は質問に答えません。そして、それは原則として間違っています。 a BETWEEN x AND y 次のように変換されます:

    a >= x AND a <= y

    含む 上限ですが、人々は通常除外する必要があります それ:

    a >= x AND a < y

    日付 簡単に調整できます。 2009年には、上限として「2009-12-31」を使用します。
    しかし、タイムスタンプではそれほど単純ではありません。 これは小数桁を許可します。最新のPostgresバージョンは、内部で8バイト整数を使用して、最大6小数秒(µsの解像度)を格納します。これを知っていると、できた それでも機能しますが、それは直感的ではなく、実装の詳細に依存します。悪い考えです。

    さらに、a BETWEEN x AND y 重複する範囲は見つかりません。必要なもの:

    b >= x AND a < y

    そして、決して離れなかった まだ考慮されていません。

    適切な回答

    年を2009と仮定します 、意味を変えずに質問を言い換えます:

    「2010年より前に参加し、2009年より前に退場しなかった特定のチームのすべてのプレーヤーを検索します。」

    基本的なクエリ:

    SELECT p.*
    FROM   team     t
    JOIN   contract c USING (name_team) 
    JOIN   player   p USING (name_player) 
    WHERE  t.name_team = ? 
    AND    c.date_join  <  date '2010-01-01'
    AND    c.date_leave >= date '2009-01-01';
    

    しかし、それだけではありません:

    参照整合性がFK制約で適用される場合、テーブルteam それ自体はクエリの単なるノイズであり、削除できます。

    同じプレーヤーが同じチームを離れて再参加することはできますが、たとえばDISTINCTを使用して、重複する可能性のあるものを折りたたむ必要もあります。 。

    そして、私たちはかもしれません 特別な場合を提供する必要があります:決して去ったことのないプレーヤー。それらのプレーヤーがdate_leaveでNULLを持っていると仮定します 。

    「去ったことが知られていないプレーヤーは、今日までチームのためにプレーしていると想定されます。」

    洗練されたクエリ:

    SELECT DISTINCT p.* 
    FROM   contract c
    JOIN   player   p USING (name_player) 
    WHERE  c.name_team = ? 
    AND    c.date_join  <  date '2010-01-01'
    AND   (c.date_leave >= date '2009-01-01' OR c.date_leave IS NULL);
    

    演算子の優先順位は、ANDに対して機能します ORの前にバインドします 。かっこが必要です。

    最適化されたDISTINCTに関連する回答 (重複が一般的である場合):

    • 多対多のテーブル-パフォーマンスが悪い

    通常、名前 自然人の数は一意ではなく、代理の主キーが使用されます。しかし、明らかに、name_player playerの主キーです 。必要なのがプレーヤー名だけの場合、テーブルplayerは必要ありません。 クエリでは、次のいずれかです。

    SELECT DISTINCT name_player 
    FROM   contract
    WHERE  name_team = ? 
    AND    date_join  <  date '2010-01-01'
    AND   (date_leave >= date '2009-01-01' OR date_leave IS NULL);
    

    SQL OVERLAPS オペレーター

    マニュアル:

    OVERLAPS ペアの以前の値を開始として自動的に取得します。各期間は、半分開いた間隔のstart <= time < endを表すと見なされます 、startでない限り およびend 等しい場合、それはその単一の瞬間を表します。

    潜在的なNULLを処理する 値、COALESCE 最も簡単なようです:

    SELECT DISTINCT name_player 
    FROM   contract
    WHERE  name_team = ? 
    AND    (date_join, COALESCE(date_leave, CURRENT_DATE)) OVERLAPS
           (date '2009-01-01', date '2010-01-01');  -- upper bound excluded
    

    インデックスをサポートする範囲タイプ

    Postgresの場合9.2以降 実際の範囲タイプで操作することもできます :

    SELECT DISTINCT name_player 
    FROM   contract
    WHERE  name_team = ? 
    AND    daterange(date_join, date_leave) &&
           daterange '[2009-01-01,2010-01-01)';  -- upper bound excluded
    

    範囲タイプはオーバーヘッドを追加し、より多くのスペースを占有します。 2xdate =8バイト; 1 x daterange =ディスクで14バイト、またはRAMで17バイト。ただし、重複演算子&&と組み合わせて クエリはGiSTインデックスでサポートできます。

    また、NULL値を特殊な場合にする必要はありません。 NULLは、範囲タイプの「オープンレンジ」を意味します。まさに必要なものです。テーブル定義を変更する必要はありません。範囲タイプをその場で作成でき、一致する式インデックスを使用してクエリをサポートできます:

    CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
    

    関連:

    • 平均在庫履歴テーブル


    1. MariaDBの「エラー1054(42S22):「on句」の不明な列「…」」を修正

    2. Oracle SQL Developerでジョブをスケジュールする方法は?

    3. MySQLにBLOBおよびCLOBファイルを挿入する方法は?

    4. RubyonRails用のUbuntuへのPostgreSQLのインストール