首先,您可以使用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.* 获取所有列。
1
in ORDER BY 1
是用于简化代码的位置参数。按第一栏排序month_and_year
。
为了加快速度,请在这些表达式上创建一个多列索引。喜欢
CREATE INDEX employees_start_finish_idx
ON employees (start_date, COALESCE(finish_date, 'infinity') DESC);
注意第二个索引列的降序。
如果您应该犯下将时间数据存储为字符串类型 ( text
orvarchar
) 的愚蠢行为,使用模式'DD/MM/YYYY'
而不是date
or timestamp
,timestamptz
请将字符串转换为 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'))
您甚至可以拥有这样的功能索引。但实际上,您应该使用date
ortimestamp
列。