当基表日期为
date
或 timestamp
时,许多查询都是按周、月或季度进行的。
一般来说,在
group by
查询中,是否使用
- 日期功能
- day
已预先计算提取量的表
注意:与 DATE 查找表 (1990/01/01:2041/12/31) 类似的问题
例如,在
postgresql
create table sale(
tran_id serial primary key,
tran_dt date not null default current_date,
sale_amt decimal(8,2) not null,
...
);
create table days(
day date primary key,
week date not null,
month date not null,
quarter date non null
);
-- week query 1: group using funcs
select
date_trunc('week',tran_dt)::date - 1 as week,
count(1) as sale_ct,
sum(sale_amt) as sale_amt
from sale
where date_trunc('week',tran_dt)::date - 1 between '2012-1-1' and '2011-12-31'
group by date_trunc('week',tran_dt)::date - 1
order by 1;
-- query 2: group using days
select
days.week,
count(1) as sale_ct,
sum(sale_amt) as sale_amt
from sale
join days on( days.day = sale.tran_dt )
where week between '2011-1-1'::date and '2011-12-31'::date
group by week
order by week;
对我来说,虽然
date_trunc()
功能看起来更有机,但 days
表格更容易使用。
这里除了口味问题还有什么吗?
-- query 3: group using instant "immediate" calendar table
WITH calender AS (
SELECT ser::date AS dd
, date_trunc('week', ser)::date AS wk
-- , date_trunc('month', ser)::date AS mon
-- , date_trunc('quarter', ser)::date AS qq
FROM generate_series( '2012-1-1' , '2012-12-31', '1 day'::interval) ser
)
SELECT
cal.wk
, count(1) as sale_ct
, sum(sa.sale_amt) as sale_amt
FROM sale sa
JOIN calender cal ON cal.dd = sa.tran_dt
-- WHERE week between '2012-1-1' and '2011-12-31'
GROUP BY cal.wk
ORDER BY cal.wk
;
注意:我修复了 BETWEEN 范围内的明显拼写错误。
更新:我使用 Erwin 的递归 CTE 来挤出重复的 date_trunc()。大量嵌套 CTE:
WITH calendar AS (
WITH RECURSIVE montag AS (
SELECT '2011-01-01'::date AS dd
UNION ALL
SELECT dd + 1 AS dd
FROM montag
WHERE dd < '2012-1-1'::date
)
SELECT mo.dd, date_trunc('week', mo.dd + 1)::date AS wk
FROM montag mo
)
SELECT
cal.wk
, count(1) as sale_ct
, sum(sa.sale_amt) as sale_amt
FROM sale sa
JOIN calendar cal ON cal.dd = sa.tran_dt
-- WHERE week between '2012-1-1' and '2011-12-31'
GROUP BY cal.wk
ORDER BY cal.wk
;
1. 你的表情:
...在“2012-1-1”和“2011-12-31”之间
BETWEEN
要求左侧参数小于或等于右侧参数。必须是:
... BETWEEN SYMMETRIC '2012-1-1' and '2011-12-31'
或者这只是一个错字,你的意思是:
... BETWEEN '2011-1-1' and '2011-12-31'
我不清楚您的查询应该检索什么。我将假设您想要从 2011 年开始的所有周(周一到周日)来完成本答案的其余部分。在现代硬件上,该表达式可以在不到一微秒的时间内准确生成该表达式(适用于任何年份):
SELECT generate_series(
date_trunc('week','2010-12-31'::date) + interval '7d'
, date_trunc('week','2011-12-31'::date) + interval '6d'
, '1d')::date
*请注意,ISO 8601 定义“一年的第一周”略有不同。
2. 您的第二个查询根本不起作用。没有
GROUP BY
?
3. 您链接到的问题没有涉及 PostgreSQL,它具有出色的日期/时间戳支持。它具有
generate_series()
,在大多数情况下可以避免需要单独的“天”表 - 如上所示。您的查询将如下所示:
与此同时,@wildplasser 提供了一个示例查询,该查询应该放在此处。
根据流行*的需求,递归 CTE 版本 - 实际上距离成为一个严肃的替代方案并不遥远!
* 我所说的“受欢迎”是指 @wildplasser 非常严肃的要求。
WITH RECURSIVE days AS (
SELECT '2011-01-01'::date AS dd
, date_trunc('week', '2011-01-01'::date )::date AS wk
UNION ALL
SELECT dd + 1
, date_trunc('week', dd + 1)::date AS wk
FROM days
WHERE dd < '2011-12-31'::date
)
SELECT d.wk
, count(*) AS sale_ct
, sum(s.sale_amt) AS sale_amt
FROM days d
JOIN sale s ON s.tran_dt = d.dd
-- WHERE d.wk between '2011-01-01' and '2011-12-31'
GROUP BY 1
ORDER BY 1;
也可以写成(与@wildplasser的版本比较):
WITH RECURSIVE d AS (
SELECT '2011-01-01'::date AS dd
UNION ALL
SELECT dd + 1 FROM d WHERE dd < '2011-12-31'::date
)
, days AS (SELECT dd, date_trunc('week', dd + 1)::date AS wk FROM d)
SELECT ...
4. 如果性能至关重要,请确保在过滤之前不对列值应用函数或计算。这禁止使用 indexes 并且通常非常慢,因为必须处理每一行。这就是为什么你的第一个查询会因为一张大表而变得糟糕。只要有可能,请将计算应用于您筛选的值。
表达式上的索引是解决这个问题的一种方法。如果你有一个像
这样的索引CREATE INDEX sale_tran_dt_week_idx ON sale (date_trunc('week', tran_dt)::date);
..您的第一个查询可能会再次变得非常快 - 需要付出一些用于索引维护的写入操作的成本。
是的,这不仅仅是品味问题。查询的性能取决于方法。
作为第一个近似值,函数应该更快。它们不需要联接,在单个表扫描中进行读取。
但是,一个好的优化器可以有效地利用查找表。它会知道目标值的分布。而且,内存中的连接可能会非常快。
作为数据库设计,我认为拥有日历表非常有用。某些信息(例如假期)无法作为函数使用。但是,对于大多数临时查询,日期函数就可以了。