SQL:比较两个表是否缺少记录,然后是日期字段

时间:2017-11-14 21:20:05

标签: sql postgresql

我有两张表格如下

work_assignments

emp_id   | start_date  |   End Date
------------------------------------------
  1      | May-10-2017 | May-30-2017
  1      | Jun-05-2017 | null
  2      | May-08-2017 | null 

hourly_pay

emp_id   | start_date  |   End Date    |  Rate
-----------------------------------------------
  1      | May-20-2017 | Jun-30-2017   |  75
  1      | Jul-01-2017 | null          |  80

这两个表共享emp_id(员工ID)外键并加入这两个我应该能够:

  1. 在hourly_pay表中找到缺少的员工记录。 根据此处的数据,查询应从work_assignments表
  2. 返回emp_id 2
  3. 查找小于工作分配start_date的hourly_pay start_date的记录。同样,根据这里的数据,查询应该返回emp_id 1(因为work_assignments.start_date具有May-10-2017,而最早的hourly_pay.start_date是在2017年5月20日)
  4. 我可以使用下面的连接查询实现结果的第一部分

    select distinct emp_id from work_contracts
    left join hourly_pay hr USING(emp_id)
    where hr.emp_id is null 
    

    我被困在第二部分,我可能需要一个相关的子查询来告诉在work_assignments start_date之前没有启动的每小时支付表记录?或者还有其他方式吗?

11 个答案:

答案 0 :(得分:3)

在内部查询中进行日期比较,然后将其包装以将其过滤为满足延迟付款标准的日期。

select * from (
    select distinct c.emp_id, 
        case when c.start_date < hr.start_date then 1 else 0 end as latePay
    from work_contracts c
        left join hourly_pay hr USING(emp_id)
) result
where latePay = 1

答案 1 :(得分:2)

这提示between条件有一些曲折,但我在连接中使用之间的运气非常糟糕。它们似乎在后面和末尾执行某种形式的交叉连接,然后过滤掉实际的连接where子句样式。我知道这不是很技术性的,但我从来没有在一个结果很好的连接中做过不相等的条件。

所以,这看似违反直觉,但我认为爆炸所有日期可能性实际上可能是你最好的选择。不知道你的日期范围实际有多大,很难说。

此外,我认为这实际上会同时满足您问题中的两个条件 - 通过告诉您所有没有相应工资率的工作任务。

针对您的实际数据进行尝试,看看它是如何工作的(以及需要多长时间)。

with pay_dates as (
  select
    emp_id, rate,
    generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as pd
  from hourly_pay
),
assignment_dates as (
  select
    emp_id, start_date,
    generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as wd
  from work_assignments
)
select
  emp_id, min (wd)::date as from_date,
  max (wd)::date as thru_date
from
  assignment_dates a
where
  not exists (
    select null
    from pay_dates p
    where p.emp_id = a.emp_id
    and a.wd = p.pd
  )
group by
  emp_id, start_date

结果应该是没有费率的所有工作分配范围:

emp     from             thru
1    '2017-05-10'    '2017-05-19'
2    '2017-05-08'    '2017-11-14'

很酷的是它还会删除任何重叠,其中部分工作分配。

- 编辑3/20/2018 -

根据您的要求,这里是对逻辑内容的细分。

with pay_dates as(
  select
    emp_id, rate,
    generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as pd
  from hourly_pay
)

这会获取hourly_pay数据,并将其分解为每个员工的记录,每天:

emp_id    rate    pay date
1         75      5/20/17
1         75      5/21/17
1         75      5/22/17
...
1         75      6/30/17
1         80      6/01/17
1         80      6/02/17
...
1         80      today

接下来,

[implied "with"]
assignment_dates as (
  select
    emp_id, start_date,
    generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as wd
  from work_assignments
)

对工作分配表有效地做同样的事情,只保留每一行中的“开始日期列”。

然后主要问题是:

select
  emp_id, min (wd)::date as from_date,
  max (wd)::date as thru_date
from
  assignment_dates a
where
  not exists (
    select null
    from pay_dates p
    where p.emp_id = a.emp_id
    and a.wd = p.pd
  )
group by
  emp_id, start_date

从上面的两个查询中得出。重要的部分是反连接:

not exists (
  select null
  from pay_dates p
  where p.emp_id = a.emp_id
  and a.wd = p.pd
)

确定当天该员工没有相应记录的每个工作任务。

所以从本质上讲,查询从两个表中获取数据范围,得出每个可能的日期组合,然后进行反连接以查看它们不匹配的位置。

虽然看似违反直觉,但要记录一条记录并将其分成多个记录,需要考虑两件事:

  1. 日期是非常有限的生物 - 即使在10年的数据中也只构成4,000个左右的记录,这对数据库来说并不多,即使乘以员工数据库也是如此。你的时间框架远不如此。

  2. 我使用=以外的联接非常非常糟糕,例如between>。在背景中它似乎是笛卡尔人然后过滤结果。相比之下,爆炸范围至少可以控制数据爆炸发生的程度。

  3. 对于笑话,我用上面的示例数据做了这个,并提出了这个,实际上看起来很准确:

    1   '2017-05-10'    '2017-05-19'
    2   '2017-05-08'    '2018-03-20'
    

    如果有任何不清楚的地方,请告诉我。

答案 2 :(得分:2)

您可以使用查询

实现第二部分
 select distinct wc.emp_id 
 from (select emp_id, min(start_date) start_date from work_contracts group by emp_id) wc
 join (select emp_id, min(start_date) start_date from hourly_pay group by emp_id) hr 
    on wc.emp_id = hr.emp_id
 where wc.start_date < hr.start_date

答案 3 :(得分:1)

您可以使用daterange类型解决此问题(因为,您基本上想要的是hourly_pay表中缺少的范围。)。

我在其中使用了以下operators

  • +范围联盟
  • -范围减法
  • &&测试范围交叉点
  • @>测试范围限制

使用这些和简单的left join,您可以编写查询以找出hourly_pay表中缺少的范围。

select     wa.emp_id, lower(dr) start_date, upper(dr) - 1 end_date
from       work_assignments wa
left join  hourly_pay hp on wa.emp_id = hp.emp_id
and        daterange(wa.start_date, wa.end_date, '[]') && daterange(hp.start_date, hp.end_date, '[]')
cross join lateral (select case
                      when hp is null then daterange(wa.start_date, wa.end_date, '[]')
                      else daterange(wa.start_date, wa.end_date, '[]')
                         + daterange(hp.start_date, hp.end_date, '[]')
                         - daterange(hp.start_date, hp.end_date, '[]')
                    end dr) dr
where      not exists (select 1
                       from   hourly_pay p
                       where  p.emp_id = wa.emp_id
                       and    daterange(p.start_date, p.end_date, '[]') @> dr)

-- emp_id | start_date | end_date
----------+------------+-------------
-- 1      | 2017-05-01 | 2017-05-19
-- 2      | 2017-05-08 | (null)

http://sqlfiddle.com/#!17/4bac0/14

答案 4 :(得分:1)

也许我对措辞感到有些不满,但这还不够吗?这将返回任何emp_id,其中有一个记录,其中每小时开始日期是在工作分配开始日期之后

select distinct wc.emp_id from work_contracts wc
left join hourly_pay hr USING(emp_id)
where hr.start_date > wc.start_date

答案 5 :(得分:1)

select distinct p.emp_id <br>
from hourly_pay p <br>
join work_assignments w on p.emp_id = w.emp_id <br>
where p.start_date < w.start_date <br>

根据原始问题中的规定要求:查找hourly_pay start_date晚于work assignments start_date的记录。同样,根据此处的数据,查询应返回emp_id 1(因为work_assignments.start_date具有5月10日至2017年,而最早的hourly_pay.start_date则发布于2017年5月20日)

这对我来说意味着他们只需要员工ID号。

答案 6 :(得分:1)

我会使用not exists / exists

select wa.empid
from work_assignments wa
where not exists (select 1 from hourly_pay hp where wa.emp_id = hp.emp_id);

和第二个:

select wa.*
from work_assignments wa
where not exists (select 1
                  from hourly_pay hp
                  where wa.emp_id = hp.emp_id and ep.start_date <= wp.start_date
                 );

关于(2)的问题非常特别。但是,我希望您能够在整个作业期间按小时付费,而不仅仅是开始日期。如果是这种情况,那么OP应该问一个新的问题。

答案 7 :(得分:1)

第二个查询很简单,

尝试以下查询

select distinct h.emp_id 
from work_assignments w inner join hourly_pay h 
on 
w.emp_id = h.emp_id
and h.start_date > w.start_date;

答案 8 :(得分:1)

查看您的数据,我可以做出以下假设:

1)对于end_date为null的员工,可以有最多一条记录这个条件适用于两个表。

2)同一员工的多个记录日期不重叠当员工有多个记录(如Emp 1)时,他/她的日期不会像[jan 1 - feb 1]和下一个记录为[jan 15-feb 20]或[jan 15-null](它们必须是非重叠时段)。

考虑到这些,下面的查询应该适合你。

SELECT hourly_pay.*
FROM work_assignments
INNER JOIN hourly_pay  USING(emp_id)
WHERE hourly_pay.start_date > work_assignments.start_date
        AND ( hourly_pay.start_date < work_assignments.end_date
             OR (work_assignments.end_date is null 
                  AND hourly_pay.end_date is null) ); 

说明:查询连接emp_id上的两个表,然后过滤

的记录

1)在hourly_pay中有start_date&gt; work_assignments中的start_date

- 和 -

2)在hourly_pay中使用start_date&lt; work_assignments中的end_date(这是必需的,因此我们可以避免比较两个表中不相关的时间段记录

-OR-

两个表记录的结束日期均为空,使用假设1(如上所述)   对于end_date为null的员工,最多可以有一条记录。

根据您的数据,此查询应返回hourly_pay中EMP 1的两个记录,作为start_date,其中包含&gt; work_assignments中的start_date。

如果您只需要EMP ID列表,您只需选择该列SELECT DISTINCT hourly_pay.emp_id ...(rest of the query)

即可

答案 9 :(得分:1)

http://sqlfiddle.com/#!17/f4595/1

  1. hourly_pay表中缺少记录;
  2. 我建议您使用not exists,而不是使用左连接然后过滤空值记录,它会更快地工作。

        SELECT w.emp_id, 'missing in the hourly_pay table' FROM work_assignments w
        WHERE NOT exists (SELECT 1 FROM hourly_pay h WHERE h.emp_id = w.emp_id)
    
    1. 记录hourly_pay start_date晚于工作分配start_date;

      SELECT w.emp_id FROM work_assignments w
      WHERE
      NOT exist (
          SELECT 1 FROM hourly_pay hp
          WHERE
              hp.start_date < w.start_date AND w.emp_id = hp.emp_id )
      
    2. 第二个查询实际上包含第一个查询的结果,因此您可以合并它们,如下所示:

      SELECT
          w.emp_id,
          (CASE WHEN ( EXISTS
                  (SELECT 1 FROM hourly_pay h
                      WHERE
                          h.emp_id = w.emp_id ) ) 
                THEN
                  'hourly_pay start_date is later'
                ELSE
                  'missing in the hourly_pay table'
                END)
      FROM
          work_assignments w
      WHERE
          NOT EXISTS (
              SELECT
                  1
              FROM
                  hourly_pay hp
              WHERE
                  hp.start_date < w.start_date
              AND w.emp_id = hp.emp_id
          )
      

答案 10 :(得分:1)

这将很好地完成工作。

SELECT DISTINCT emp_id 
FROM work_assingment 
JOIN hourly_pay hr USING(emp_id)
WHERE hr.start_date < work_assingment.start_date;