month 列似乎是多余的。把它从桌子上放下。 start 拥有您需要的所有信息。
(我宁愿不使用 start 作为列名,因为那是 keyword in standard SQL - 即使在 Postgres 中允许。)
SELECT date_trunc('month', start) AS mon, count(*) AS ct
FROM tbl
WHERE start >= '2021-01-01'
AND start < '2021-07-01'
GROUP BY 1
ORDER BY 1;
使用date_trunc() 保留时间顺序。如果您需要结果中的月份名称:
WITH cte(current_mon) AS (SELECT date_trunc('month', LOCALTIMESTAMP))
SELECT to_char(mon, 'Mon') AS month, COALESCE(data.ct, 0) AS ct
FROM cte c
CROSS JOIN generate_series(c.current_mon - interval '6 mon'
, c.current_mon - interval '1 mon'
, interval '1 mon') mon
LEFT JOIN (
SELECT date_trunc('month', start) AS mon, count(*)::int AS ct
FROM tbl, cte c
WHERE start >= c.current_mon - interval '6 mon'
AND start < c.current_mon
GROUP BY 1
) data USING (mon)
ORDER BY mon;
db小提琴here
每月返回一行,按时间顺序(也考虑到年份,尽管它不在您的输出中!),并且是真正动态的。
| month |
ct |
| Jan |
31 |
| Feb |
28 |
| Mar |
31 |
| Apr |
0 |
| May |
31 |
| Jun |
30 |
请注意我如何在第一个子查询mon 中使用generate_series() 首次构建过去六个月(不包括当前)的时间戳。见:
然后LEFT JOIN 从相关时间范围每月计数。这种方式总是返回最后 6 个月,即使根本没有找到任何行。 COALESCE 在这种情况下将计数设为 0 而不是 NULL。相关:
请特别注意,先聚合再加入会更快。见:
使用标准英文月份名称和 3 个字母缩写。
您的原始查询以旋转形式生成该信息:每列一个月。 但静态 SQL 查询无法使用动态列名。如果您确实需要,则需要两步操作流程(两次往返服务器):
- 构建查询。
- 执行它。
好吧,您可以准备 12 种不同的行类型(这是您的情况可能的结果类型的范围)并使用多态函数来实现它。但是您真的需要旋转表单吗?
好的,你要的...
你想要这样一个简单的电话吗?
SELECT * FROM f_tbl_counts_6months(NULL::m6_jul);
这是可能的。这是一个概念证明。
但是,老实说,我宁愿避免复杂化,只使用上面的简单查询。
创建多态函数:
CREATE OR REPLACE FUNCTION f_tbl_counts_6months(ANYELEMENT)
RETURNS SETOF ANYELEMENT
LANGUAGE plpgsql AS
$func$
DECLARE
_current_mon timestamp := date_trunc('month', LOCALTIMESTAMP);
BEGIN
-- to prevent incorrect column names, input row type must match current date:
IF right(pg_typeof($1)::text, 3) = to_char(_current_mon, 'mon') THEN
-- all good!
ELSE
RAISE EXCEPTION 'Current date is %. Function requires input >>%<<'
, CURRENT_DATE, 'NULL::m6_' || to_char(now(), 'mon');
END IF;
RETURN QUERY
SELECT a[2], a[2], a[3], a[4], a[5], a[6]
FROM (
SELECT ARRAY(
SELECT COALESCE(data.ct, 0)
FROM generate_series(_current_mon - interval '6 mon'
, _current_mon - interval '1 mon'
, interval '1 mon') mon
LEFT JOIN (
SELECT date_trunc('month', start) AS mon, count(*)::int AS ct
FROM tbl
GROUP BY 1
) data USING (mon)
ORDER BY mon
)
) sub(a);
END
$func$;
还有 12 种复合(行)类型,一年中的每个月一种:
CREATE TYPE m6_jan AS ("Jul" int, "Aug" int, "Sep" int, "Oct" int, "Nov" int, "Dec" int);
CREATE TYPE m6_feb AS ("Aug" int, "Sep" int, "Oct" int, "Nov" int, "Dec" int, "Jan" int);
CREATE TYPE m6_mar AS ("Sep" int, "Oct" int, "Nov" int, "Dec" int, "Jan" int, "Feb" int);
CREATE TYPE m6_apr AS ("Oct" int, "Nov" int, "Dec" int, "Jan" int, "Feb" int, "Mar" int);
CREATE TYPE m6_may AS ("Nov" int, "Dec" int, "Jan" int, "Feb" int, "Mar" int, "Apr" int);
CREATE TYPE m6_jun AS ("Dec" int, "Jan" int, "Feb" int, "Mar" int, "Apr" int, "May" int);
CREATE TYPE m6_jul AS ("Jan" int, "Feb" int, "Mar" int, "Apr" int, "May" int, "Jun" int);
CREATE TYPE m6_aug AS ("Feb" int, "Mar" int, "Apr" int, "May" int, "Jun" int, "Jul" int);
CREATE TYPE m6_sep AS ("Mar" int, "Apr" int, "May" int, "Jun" int, "Jul" int, "Aug" int);
CREATE TYPE m6_oct AS ("Apr" int, "May" int, "Jun" int, "Jul" int, "Aug" int, "Sep" int);
CREATE TYPE m6_nov AS ("May" int, "Jun" int, "Jul" int, "Aug" int, "Sep" int, "Oct" int);
CREATE TYPE m6_dec AS ("Jun" int, "Jul" int, "Aug" int, "Sep" int, "Oct" int, "Nov" int);
然后简单的函数调用就可以正常工作并返回您想要的结果:
SELECT * FROM f_tbl_counts_6months(NULL::m6_jul);
| Jan |
Feb |
Mar |
Apr |
May |
Jun |
| 31 |
28 |
31 |
0 |
31 |
30 |
为什么?如何?见:
您需要使用正确的类型来调用。我内置了一个故障保险装置以防止错误的结果。如果您使用错误的类型调用,例如 7 月(当前)的以下调用:
SELECT * FROM f_tbl_counts_6months(NULL::m6_nov);
...函数抛出异常并带有指令:
ERROR: Current date is 2021-07-15. Function requires input >>NULL::m6_jul<<
CONTEXT: PL/pgSQL function f_tbl_counts_6months(anyelement) line 9 at RAISE