SQL日期重叠

SQL日期重叠,sql,database,postgresql,Sql,Database,Postgresql,我只想选择拥有多个职位但在任何给定时间仅持有一个职位的员工的id。如果结束日期为空,则表示该日期是他/她的当前职位 对于下面的例子,我想得到1,3 id | position | start_date | end_date ---------------------------------------------- 0 | staff | 2005-01-01 | 2006-01-01 0 | secretary | 2006-01-02 |

我只想选择拥有多个职位但在任何给定时间仅持有一个职位的员工的id。如果结束日期为空,则表示该日期是他/她的当前职位

对于下面的例子,我想得到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

这可以通过两种方式实现。如果您真的只需要ID,那么执行两步查询是一种选择

首先获取具有多个位置的所有行:

select s1.id
from staff s1
where exists (select 1 
              from staff s2
              where s1.id = s2.id
              and s1.position <> s2.position)
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
--
 1
 3
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
如果需要所有列和所有位置,而不仅仅是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))
             )
我不确定这些是否是最有效的方法,但我现在想不出其他方法

-- 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;
这利用了,当您有开始日期和结束日期时,使用它总是很方便,因为它允许检查与&&运算符的重叠

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