2016-05-07 8 views
2

複数のポジションを持ち、任意の時点で1つしか保持していないスタッフのIDだけを選択したいと思います。終了日がNULLの場合は、現在の位置であることを意味します。以下の例SQLの日付の重複

、私はこれは、2つの方法でアプローチすることができます1、3

id | position | start_date | end_date 
---------------------------------------------- 
    0 | staff  | 2005-01-01 | 2006-01-01 
    0 | secretary | 2006-01-02 | 
    0 | assistant | 2006-01-02 | 
    1 | staff  | 2005-01-01 | 2006-01-01 
    1 | assistant | 2006-01-02 | 
    2 | receptionist | 2005-01-01 | 
    3 | driver  | 2005-01-01 | 2007-01-01 
    3 | operator  | 2007-01-02 | 
    3 | intern  | 2002-01-01 | 2002-03-01 

答えて

2

を取得したいのですが。実際にIDだけが必要な場合は、2段階クエリを実行することも1つの選択肢です。

select s1.id 
from staff s1 
where exists (select 1 
       from staff s3 
       where s1.id = s3.id 
       and s1.position <> s3.position 
       and (s1.start_date, coalesce(s1.end_date, 'infinity'::date)) overlaps (s3.start_date, coalesce(s3.end_date, 'infinity'::date)) 
      ) 

nullとして缶」:

select s1.id 
from staff s1 
where exists (select 1 
       from staff s2 
       where s1.id = s2.id 
       and s1.position <> s2.position) 

あなたが使用することができ、同時に複数のポジションを持っていたものを取得するには:

まず、複数のポジションを持っているすべての行を取得します比較するには、nullの値をend_dateに置き換えて、他のすべての日付よりも大きい日付にする必要があります。これはcoalesce(s3.start_date, 'infinity'::date)の機能です。 overlaps演算子は、重複する日付範囲をチェックします。

select s1.id 
from staff s1 
where exists (select 1 
       from staff s2 
       where s1.id = s2.id 
       and s1.position <> s2.position) 

except 

select s1.id 
from staff s1 
where exists (select 1 
       from staff s3 
       where s1.id = s3.id 
       and s1.position <> s3.position 
       and (s1.start_date, coalesce(s1.end_date, 'infinity'::date)) overlaps (s3.start_date, coalesce(s3.end_date, 'infinity'::date)) 
      ) 
; 

をあなたはEXCEPT演算子でこれらを組み合わせると、あなたが希望する結果を得るCOALESCEでdate '9999-12-31'のようなものを使用しても動作しますが、infinityを使用すると、(少なくとも私の目には)これがより明示的になります

id 
-- 
1 
3 

を使用すると、すべての列とすべてのPOSITが必要な場合:あなたのサンプルデータについて上記のクエリが返す

イオン(の代わりにのID)を使用すると、別のアプローチをとることができます。

まずポジションをオーバーラップしていないすべての行を取得:

select s1.* 
from staff s1 
where not exists (select 1 
        from staff s2 
        where s1.id = s3.id 
        and s1.position <> s3.position 
        and (s1.start_date, coalesce(s1.end_date, 'infinity'::date)) overlaps (s2.start_date, coalesce(s2.end_date, 'infinity'::date)) 
      ) 

以上ももはやアクティブ位置とID = 0を持つ行が含まれますので、我々が持っているすべてのこれらの行を削除する必要があります複数の位置:あなたのサンプルデータについては

select * 
from (
    select s1.*, 
     count(*) over (partition by s1.id) as cnt 
    from staff s1 
    where not exists (select 1 
        from staff s2 
        where s1.id = s2.id 
         and s1.position <> s2.position 
         and (s1.start_date, coalesce(s1.end_date, 'infinity'::date)) overlaps (s2.start_date, coalesce(s2.end_date, 'infinity'::date)) 
       ) 
) t 
where cnt > 1; 

は、上記戻ります:

id | position | start_date | end_date | cnt 
---+-----------+------------+------------+---- 
1 | staff  | 2005-01-01 | 2006-01-01 | 2 
1 | assistant | 2006-01-02 |   | 2 
3 | driver | 2005-01-01 | 2007-01-01 | 3 
3 | operator | 2007-01-02 |   | 3 
3 | intern | 2002-01-01 | 2002-03-01 | 3 

これが最も効率的な方法であるかどうかはわかりませんが、今は別のものを考えることはできません。

2
-- First select all id's that have held more than one position: 0, 1, 3 
SELECT id 
FROM personnel 
GROUP BY id 
HAVING count(id) > 1 

EXCEPT 

-- Now remove id's that had an overlap in positions: 0 
SELECT DISTINCT sub1.id 
FROM (
    SELECT id, position, daterange(start_date, end_date, '[]') AS period 
    FROM personnel) sub1 
JOIN (
    SELECT id, position, daterange(start_date, end_date, '[]') AS period 
    FROM personnel) sub2 
ON sub1.id = sub2.id AND sub1.period && sub2.period AND sub1.position <> sub2.position; 

SQLfiddle

これは、常にそれが&&オペレータとの重複をチェックすることができますよう、あなたが開始と終了日を持っているときに使用する便利である、the daterange typeを使用しています。

関連する問題