SQL日期重叠

时间:2016-05-07 04:21:28

标签: sql database postgresql

我只想选择有多个职位但在任何时候只持有一个职位的员工的身份。如果结束日期为NULL,则表示它是他/她的当前位置。

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

答案 0 :(得分:2)

这可以通过两种方式来解决。如果您真的只需要ID,那么执行两步查询就是一个选项。

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

char A[1000];

FILE * fpointer;
fpointer=fopen("text.txt","r");

i=0;
while(!feof(fpointer))
{
    fscanf(fpointer,"%c",&A[i]);
    i=i+1;
}

fclose(fpointer);

for (i=0;i<100;i++)
{
    printf("%c",A[i]);
}

return 0;

要获得同时拥有多个职位的人,您可以使用:

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

由于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中的null值替换为大于所有其他日期的日期。这就是end_date的作用。 coalesce(s3.start_date, 'infinity'::date)运算符检查重叠的日期范围。在合并中使用类似overlaps的内容也可以,但使用date '9999-12-31'可以使其更加明确(至少在我看来)

将这些与infinity运算符组合使用时,可以得到所需的结果:

EXCEPT

对于您的示例数据,上述查询将返回:

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

如果您需要所有列和所有位置(而不是 ID),您可以采用不同的方法。

首先获取所有没有重叠位置的行:

id
--
 1
 3

上面还将包含id = 0且不再有效位的行,因此我们需要删除所有具有多个位置的行:

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

对于您的样本数据,上面将返回:

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;

我不确定这些是否是最有效的方法,但我现在无法想到别的东西。

答案 1 :(得分: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,当您有一个开始和结束日期时,它总是很方便使用,因为它允许检查与&&运算符的重叠。