在MySQL中查找序列中缺少的数据

时间:2017-12-30 03:50:58

标签: mysql aggregate-functions

是否有一种有效的方法可以不仅在一个序列中找到缺失的数据,而且还有许多序列?

这可能是不可避免的O(N ** 2),所以这里的效率很高,定义为使用MySQL的查询相对较少

我们假设我有一张临时员工及其开始和结束月份的表格。

employees  | start_month | end_month
------------------------------------
Jane         2017-05       2017-07
Bob          2017-10       2017-12

还有一份与这些员工每月付款的相关表格

employee | paid_month
---------------------
Jane       2017-05
Jane       2017-07
Bob        2017-11
Bob        2017-12

现在,很清楚我们在Jane(2017-06)上错过了一个月,而在Bob(2017-10)也错过了一个月。

有没有办法以某种方式找到他们的付款记录中的差距,而不会来回走动?

如果只有一个序列需要检查,有些人会生成一个有效值的临时表,然后LEFT JOIN来查找间隙。但在这里,我们为每位员工提供不同的顺序。

一种可能性是我们可以进行聚合查询以查找每个员工的paid_months的COUNT(),然后检查它与预期的月份增量之间的差异。不幸的是,这里的数据有点脏,所以我们实际上有可能在该员工开始或结束日期之前或之后的付款日期。但我们正在核实官方序列肯定有付款。

2 个答案:

答案 0 :(得分:0)

形成员工和月份的笛卡尔积,然后将实际数据加入其中,然后在没有与笛卡尔积的匹配付款时显示缺失的数据。

您需要每月的清单。这可能来自您已经拥有的“日历表”,或者,如果每个月都在源数据中表示,则可以使用子查询 MIGHT

e.g。

select
    m.paid_month, e.employee 
from (select distinct paid_month from payments) m
cross join (select employee from employees) e
left join payments p on m.paid_month = p.paid_month and e.employee = p.employee
where p.employee is null

子查询m可以用日历表或其他一些技术代替,以生成一系列月份。 e.g。

select 
        DATE_FORMAT(m1, '%Y-%m')
from (
        select 
              '2017-01-01'+ INTERVAL m MONTH as m1
        from (
            select @rownum:=@rownum+1 as m 
            from (select 1 union select 2 union select 3 union select 4) t1
            cross join (select 1 union select 2 union select 3 union select 4) t2
            ## cross join (select 1 union select 2 union select 3 union select 4) t3
            ## cross join (select 1 union select 2 union select 3 union select 4) t4
            cross join(select @rownum:=-1) t0
            ) d1
        ) d2 
where m1 < '2018-01-01'
order by m1

子查询e可以包含其他逻辑(例如,确定哪些员工目前仍在使用,或者是“临时员工”)

答案 1 :(得分:0)

首先,我们需要在开始日期和结束日期之间的所有月份中获取一个临时表,然后需要在付费月份上使用付款表进行左外连接,过滤所有不匹配的月份(付款员工姓名为空)

select e.employee, e.yearmonth as missing_paid_month from (
 with t as (
     select e.employee, to_date(e.start_date, 'YYYY-MM') as start_date, to_date(e.end_date, 'YYYY-MM') as end_date from employees e
  )
  select distinct t.employee, 
  to_char(add_months(trunc(start_date,'MM'),level - 1),'YYYY-MM') yearmonth
  from  t  
  connect by trunc(end_date,'mm') >= add_months(trunc(start_date,'mm'),level - 1)
  order by t.employee,  yearmonth
 ) e
left outer join payments p
on p.paid_month = e.yearmonth
where p.employee is null

输出

EMPLOYEE    MISSING_PAID_MONTH
Bob         2017-10
Jane        2017-06

SQL Fiddle http://sqlfiddle.com/#!4/2b2857/35