0

我很难通过写作来解释这一点,所以请耐心等待。

我正在做这个项目,在这个项目中,我必须选择一个月和一年来了解一年中那个月份的所有在职员工。但在我的数据库中,我存储了他们在 dd 中开始和结束的日期/mm/yyyy 格式。

因此,如果我有一个工作了 4 个月的员工,例如。从 01/01/2013 到 01/05/2013 我将在四个月内拥有他。我需要让他出现 4 张桌子(每个活跃月份一张),以及那些月份活跃的其他员工。在这种情况下,这些将是:2013 年 1 月、2 月、3 月和 4 月。

问题是我不知道如何在这里进行查询或 php 处理来实现这一点。

我能想到的只是(我每个月都会运行这个查询,将年份和月份作为参数传递)

pg_query= "SELECT employee_name FROM employees
           WHERE month_and_year between start_date AND finish_date"

但这做不到,主要是因为month_and_year必须是列而不是变量。
任何人的想法?

更新

是的,我很抱歉我忘了说我使用 DATE 作为数据类型。

我发现最简单的解决方案是使用 EXTRACT

select * from employees where extract (year FROM start_date)>='2013'
AND extract (month FROM start_date)='06' AND extract (month FROM finish_date)<='07'

这给了我 2013 年 6 月以来的所有记录,您确定可以用文字变量替换您喜欢的任何变量

4

3 回答 3

2

无需创建范围即可进行重叠:

select to_char(d, 'YYYY-MM') as "Month", e.name
from
    (
        select generate_series(
            '2013-01-01'::date, '2013-05-01', '1 month'
        )::date
    ) s(d)
    inner join
    employee e on
        date_trunc('month', e.start_date)::date <= s.d
        and coalesce(e.finish_date, 'infinity') > s.d
order by 1, 2

SQL小提琴

如果您希望显示没有在职员工的月份,inner则将left join


欧文,关于你的评论:

第二个表达式必须是coalesce(e.finish_date, 'infinity') >= s.d

注意要求:

因此,如果我有一个工作了 4 个月的员工,例如。从 01/01/2013 到 01/05/2013 我将在四个月内拥有他

据我了解,最后一个活跃日确实是完成后的前一天。

如果我使用您的“修复”,我将在我的示例f中包含月份中的员工。05他完成了2013-05-01

('f', '2013-04-17', '2013-05-01'),

SQL Fiddle 与您的修复

于 2013-07-04T23:56:03.697 回答
2

假设您确实没有将日期存储为字符串,而只是以这种方式输出它们,那么您可以这样做:

SELECT employee_name
FROM employees
WHERE start_date <= <last date of month> and
      (finish_date >= <first date of month> or finish_date is null)

如果您以这种格式存储它们,那么您可以对年份和月份进行一些摆弄。
此版本将“日期”转换为“YYYYMM”形式的字符串。只需像这样表达您想要的月份,您就可以进行比较:

select employee_name
from employees e
where right(start_date, 4)||substr(start_date, 4, 2) <= 'YYYYMM' and
      (right(finish_date, 4)||substr(finish_date, 4, 2) >= 'YYYYMM' or finish_date is null)

注意:表达式'YYYYMM'是指您要查找的月份/年份。

于 2013-07-04T19:00:06.337 回答
2

首先,您可以使用generate_series(). 要获得下限和上限,请在开始时添加 1 个月的间隔:

SELECT g::date                       AS d_lower
    , (g + interval '1 month')::date AS d_upper
FROM  generate_series('2013-01-01'::date, '2013-04-01', '1 month') g;

产生:

  d_lower   |  d_upper
------------+------------
 2013-01-01 | 2013-02-01
 2013-02-01 | 2013-03-01
 2013-03-01 | 2013-04-01
 2013-04-01 | 2013-05-01

时间范围的上边界是下个月的第一天。这是故意的,因为我们将进一步使用标准 SQLOVERLAPS运算符。在所述位置引用手册:

每个时间段都被认为代表半开区间 start <= time < end [...]

接下来,您使用 aLEFT [OUTER] JOIN将员工连接到这些日期范围:

SELECT to_char(m.d_lower, 'YYYY-MM') AS month_and_year, e.*
FROM  (
   SELECT g::date                       AS d_lower
       , (g + interval '1 month')::date AS d_upper
   FROM   generate_series('2013-01-01'::date, '2013-04-01', '1 month') g
   ) m
LEFT   JOIN employees e ON (m.d_lower, m.d_upper)
                  OVERLAPS (e.start_date, COALESCE(e.finish_date, 'infinity'))
ORDER  BY 1;
  • LEFT JOIN即使没有找到匹配的员工,也包括日期范围。

  • 用于COALESCE(e.finish_date, 'infinity'))没有finish_date. 他们被认为仍在工作。或者也许使用current_date代替infinity.

  • 用于to_char()获得格式良好的month_and_year值。

  • 您可以轻松地从中选择所需的任何列employees。在我的示例中,我使用 e.* 获取所有列。

  • 1in ORDER BY 1是用于简化代码的位置参数。按第一栏排序month_and_year

  • 为了加快速度,请在这些表达式上创建一个多列索引。喜欢

    CREATE INDEX employees_start_finish_idx
    ON employees (start_date, COALESCE(finish_date, 'infinity') DESC);
    

    注意第二个索引列的降序。

  • 如果您应该犯下将时间数据存储为字符串类型 ( textorvarchar ) 的愚蠢行为,使用模式'DD/MM/YYYY'而不是dateor timestamptimestamptz请将字符串转换为 date with to_date()。例子:

    SELECT to_date('01/03/2013'::text, 'DD/MM/YYYY')
    

    将查询的最后一行更改为:

    ...
    OVERLAPS (to_date(e.start_date, 'DD/MM/YYYY')
             ,COALESCE(to_date(e.finish_date, 'DD/MM/YYYY'), 'infinity'))
    

    您甚至可以拥有这样的功能索引。但实际上,您应该使用dateortimestamp列。

于 2013-07-04T20:40:26.723 回答