什么是Oracle数据库中“帮助器”表的更好替代方案?

时间:2018-07-31 16:52:14

标签: sql oracle

假设我有一个“员工”表,其中包含员工的开始和结束日期,如下所示:

员工

employee_id   start_date   end_date
53            '19901117'   '99991231'
54            '19910208'   '20010512'
55            '19910415'   '20120130'
.             .            .
.             .            .
.             .            .

假设我想获取月底受雇的员工的月度数量。因此,我需要的结果数据集将如下所示:

month        count of employees
'20150131'   120
'20150228'   118
'20150331'   122
.            .
.            .
.            .

我目前知道如何执行此操作的最佳方法是创建一个要连接到的“帮助程序”表,例如:

helper_tbl

month
'20150131'
'20150228'
'20150331'
.
.
.

然后执行如下查询:

SELECT t0b.month,
        count(t0a.employee_id)
FROM employees t0a
JOIN helper_tbl t0b
ON t0b.month BETWEEN t0a.start_date AND t0a.end_date
GROUP BY t0b.month

但是,这对我来说是个烦人的解决方案,因为这意味着我必须一直创建这些小助手表,并且它们会使我的架构混乱。我觉得其他人也必须遇到同样需要“ helper”表的情况,但是我猜人们已经找到了一种更好的方法来解决此问题,而这并不是那么手工。还是你们真的像我一样一直在创建“ helper”表来解决这些情况?

我知道这个问题对于堆栈溢出来说有点开放,所以让我提供一个更为封闭的问题版本:“ 仅给出'employees'表,您会怎么做?以获得上面显示的结果数据集?

2 个答案:

答案 0 :(得分:1)

您可以使用CTE生成所有月份值,这些值可以是固定的起点,也可以基于表格中最早的日期生成:

with months (month) as (
  select add_months(first_month, level - 1)
  from (
    select trunc(min(start_date), 'MM') as first_month from employees
  )
  connect by level <= ceil(months_between(sysdate, first_month))
)
select * from months;

以您的示例中最早的开始日期为1990-11-17的数据为例,它会生成333行:

MONTH              
-------------------
1990-11-01 00:00:00
1990-12-01 00:00:00
1991-01-01 00:00:00
1991-02-01 00:00:00
1991-03-01 00:00:00
...
2018-06-01 00:00:00
2018-07-01 00:00:00

然后,您可以在连接到表的查询中使用它,例如:

with months (month) as (
  select add_months(first_month, level - 1)
  from (
    select trunc(min(start_date), 'MM') as first_month from employees
  )
  connect by level <= ceil(months_between(sysdate, first_month))
)
select m.month, count(*) as employees
from months m
left join employees e
on e.start_date <= add_months(m.month, 1)
and (e.end_date is null or e.end_date >= add_months(m.month, 1))
group by m.month
order by m.month;

想必您希望包括仍在工作的人员,因此您需要允许结束日期为空(除非您对仍在工作的人员使用不可思议的结束日期值...)

将日期存储为字符串会有点复杂,但是您可以通过类似的方式生成月份信息:

with months (month, start_date, end_date) as (
  select add_months(first_month, level - 1),
    to_char(add_months(first_month, level - 1), 'YYYYMMDD'),
    to_char(last_day(add_months(first_month, level - 1)), 'YYYYMMDD')
  from (
    select trunc(min(to_date(start_date, 'YYYYMMDD')), 'MM') as first_month from employees
  )
  connect by level <= ceil(months_between(sysdate, first_month))
)
select m.month, m.start_date, m.end_date, count(*) as employees
from months m
left join employees e
on e.start_date <= m.end_date
and (e.end_date is null or e.end_date > m.end_date)
group by m.month, m.start_date, m.end_date
order by m.month;

使用少量的虚假数据进行了非常轻松的测试,并且似乎都可以正常工作。

答案 1 :(得分:0)

如果要获取本月底雇用的员工,则可以在查询的WHERE子句中使用LAST_DAY函数。另外,您可以在查询的GROUP BY子句中使用该函数。因此您的查询将如下所示,

SELECT LAST_DAY(start_date), COUNT(1)
  FROM employees
 WHERE start_date = LAST_DAY(start_date)
 GROUP BY LAST_DAY(start_date)

或者如果您只想计算每月雇用的员工,则使用以下查询,

SELECT LAST_DAY(start_date), COUNT(1)
  FROM employees
 GROUP BY LAST_DAY(start_date)