我在sql表中有两列是startdate和enddate
Startdate Enddate
27-12-2015 22:30 03-01-2016 19:30
01-01-2016 12:45 09-02-2016 18:30
我想得到像
这样的结果表Startdate Enddate Month year days
27-12-2015 22:30 03-01-2016 19:30 Dec 2015 5
27-12-2015 22:30 03-01-2016 19:30 Jan 2016 3
01-01-2016 12:45 09-02-2016 18:30 Jan 2016 31
01-01-2016 12:45 09-02-2016 18:30 Feb 2016 9
答案 0 :(得分:2)
在这种特殊情况下,plpgsql函数可以提供比普通sql查询更好的性能。
create or replace function get_months(startdate date, enddate date)
returns table (mon text, year int, days int)
language plpgsql as $$
declare d date;
begin
d:= date_trunc('month', startdate);
while d < enddate loop
mon:= to_char(d, 'Mon');
year:= to_char(d, 'YYYY');
days:= case
when d+ '1month'::interval > enddate then enddate- d+ 1
when d < startdate then (d+ '1month'::interval)::date- startdate
else (d+ '1month'::interval)::date- d
end;
return next;
d:= d+ '1month'::interval;
end loop;
end
$$;
测试:
with my_table(startdate, enddate) as (
values
('2015-12-27 22:30', '2016-01-03 19:30'),
('2016-01-01 12:45', '2016-02-09 18:30')
)
select *
from my_table,
lateral get_months(startdate::date, enddate::date)
startdate | enddate | mon | year | days
------------------+------------------+-----+------+------
2015-12-27 22:30 | 2016-01-03 19:30 | Dec | 2015 | 5
2015-12-27 22:30 | 2016-01-03 19:30 | Jan | 2016 | 3
2016-01-01 12:45 | 2016-02-09 18:30 | Jan | 2016 | 31
2016-01-01 12:45 | 2016-02-09 18:30 | Feb | 2016 | 9
(4 rows)
答案 1 :(得分:1)
粗略的解决方案是生成所有日子然后聚合(计数)它们。它有效,但记忆力很差。如果它不重要,这个解决方案肯定会奏效。如果性能至关重要,另一种方法是生成一个月系列,并在很多条件下进行日间差异。
SELECT
dates.startdate::DATE,
dates.enddate::DATE,
to_char(days.s, 'Mon') AS mon,
to_char(days.s, 'YYYY') AS yr,
count(1) AS d
FROM dates
CROSS JOIN LATERAL (
SELECT * FROM generate_series(dates.startdate, dates.enddate, INTERVAL '1 day') s
) days
GROUP BY 1, 2, 3, 4
在任何情况下,这是第二个变体,它会循环数月(更快,更难理解):
SELECT
dates.startdate::DATE,
dates.enddate::DATE,
to_char(months.startdate, 'Mon') AS mon,
to_char(months.startdate, 'YYYY') AS yr,
least(
months.enddate::DATE - dates.startdate::DATE + 1, -- takes care of first month
dates.enddate::DATE - months.startdate::DATE + 1, -- takes care of last month
months.enddate::DATE - months.startdate::DATE + 1 -- takes care of full months from the middle of the intervals
) AS "days"
FROM dates
-- get months as first day in that month
CROSS JOIN LATERAL (
SELECT * FROM generate_series(
(to_char(dates.startdate, 'YYYY-MM-') || '01')::DATE,
(to_char(dates.enddate + INTERVAL '1 month', 'YYYY-MM-') || '01')::DATE - 1, INTERVAL '1 month') m
) days
-- get months as start date and end date
CROSS JOIN LATERAL (
SELECT
days.m::DATE AS startdate,
(days.m + INTERVAL '1 month')::DATE - 1 AS enddate
) months