【问题标题】:What is a better alternative to a "helper" table in an Oracle database?Oracle 数据库中的“帮助”表有什么更好的替代方法?
【发布时间】:2018-08-01 13:30:17
【问题描述】:

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

员工

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

但是,这对我来说有点烦人,因为这意味着我必须一直创建这些小帮助表,并且它们会弄乱我的架构。我觉得其他人必须遇到对“帮助”表的同样需求,但我猜人们已经找到了一种更好的方法来解决这个问题,而不是那么手动。或者你们真的只是像我一样继续创建“帮助”表来解决这些情况吗?

我知道这个问题对于堆栈溢出有点开放,所以让我提供一个更封闭的问题版本,“只考虑'员工'表,你会怎么做得到我上面显示的结果数据集?"

【问题讨论】:

  • 这些列是什么数据类型 - 字符串或日期?你让它们看起来像字符串,这会有点奇怪。
  • employee_id 的数字和日期的字符串。我的公司使用这样的字符串作为日期列(我知道,这很糟糕)。

标签: sql oracle


【解决方案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;

用少量虚构的数据进行了非常轻微的测试,两者似乎都有效。

【讨论】:

  • 这太棒了,谢谢,而且比我过去所做的要优雅得多。直到今天我才遇到“CONNECT BY”子句。我会从中得到很多好处。我非常感谢您提供了一个使用日期作为字符串的版本。
  • 好答案。我喜欢使用公用表表达式。他们翻译到其他数据库:WITH monthgen ( prevmonth ) AS (SELECT DATE '2015-06-01' prevmonth FROM DUAL UNION ALL SELECT ADD_MONTHS ( prevmonth, 1 ) FROM monthgen WHERE prevmonth &lt;= SYSDATE) SELECT * FROM monthgen;
【解决方案2】:

如果您想获取月底就业的员工,那么您可以在查询的 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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-28
    • 2018-10-09
    • 2010-09-22
    • 1970-01-01
    • 2022-11-16
    • 2023-02-10
    相关资源
    最近更新 更多