我建立在Postgres数据库那里将是一个很大的事情分组month
并year
通过,但从来没有date
。
- 我可以创建整数
month
和year
列并使用它们。 - 或者我可以有一
month_year
列,并始终将其设置day
为1。
如果有人正在查看数据,则前者似乎更简单明了,但后者的好处在于它使用了适当的类型。
我建立在Postgres数据库那里将是一个很大的事情分组month
并year
通过,但从来没有date
。
month
和year
列并使用它们。month_year
列,并始终将其设置day
为1。如果有人正在查看数据,则前者似乎更简单明了,但后者的好处在于它使用了适当的类型。
Answers:
就个人而言,如果是日期,或者可以是日期,我建议始终将其存储为一个。根据经验,使用起来更容易。
您可以使用一个日期来支持日期(如果需要),也可以使用一个日期smallint
(年份和月份)来支持精确度。
现在让我们看一个示例。为示例创建100万个日期。在1901年到2100年之间的200年中,这大约是5,000行。每年每个月应该有一些东西。
CREATE TABLE foo
AS
SELECT
x,
make_date(year,month,1)::date AS date,
year::smallint,
month::smallint
FROM generate_series(1,1e6) AS gs(x)
CROSS JOIN LATERAL CAST(trunc(random()*12+1+x-x) AS int) AS month
CROSS JOIN LATERAL CAST(trunc(random()*200+1901+x-x) AS int) AS year
;
CREATE INDEX ON foo(date);
CREATE INDEX ON foo (year,month);
VACUUM FULL ANALYZE foo;
WHERE
现在,我们可以测试不使用日期的这些理论了。我分别运行了几次,以使它们变暖。
EXPLAIN ANALYZE SELECT * FROM foo WHERE date = '2014-1-1'
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on foo (cost=11.56..1265.16 rows=405 width=14) (actual time=0.164..0.751 rows=454 loops=1)
Recheck Cond: (date = '2014-04-01'::date)
Heap Blocks: exact=439
-> Bitmap Index Scan on foo_date_idx (cost=0.00..11.46 rows=405 width=0) (actual time=0.090..0.090 rows=454 loops=1)
Index Cond: (date = '2014-04-01'::date)
Planning time: 0.090 ms
Execution time: 0.795 ms
现在,让我们尝试将它们分开的另一种方法
EXPLAIN ANALYZE SELECT * FROM foo WHERE year = 2014 AND month = 1;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on foo (cost=12.75..1312.06 rows=422 width=14) (actual time=0.139..0.707 rows=379 loops=1)
Recheck Cond: ((year = 2014) AND (month = 1))
Heap Blocks: exact=362
-> Bitmap Index Scan on foo_year_month_idx (cost=0.00..12.64 rows=422 width=0) (actual time=0.079..0.079 rows=379 loops=1)
Index Cond: ((year = 2014) AND (month = 1))
Planning time: 0.086 ms
Execution time: 0.749 ms
(7 rows)
公平地讲,它们并不是全部为0.749 ..有些或多或少都可以,但这并不重要。它们都相对相同。根本不需要它。
现在,让我们玩得开心。假设您想查找2014年1月的1个月内(我们上面使用的同一个月)的所有时间间隔。
EXPLAIN ANALYZE
SELECT *
FROM foo
WHERE date
BETWEEN
('2014-1-1'::date - '1 month'::interval)::date
AND ('2014-1-1'::date + '1 month'::interval)::date;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on foo (cost=21.27..2310.97 rows=863 width=14) (actual time=0.384..1.644 rows=1226 loops=1)
Recheck Cond: ((date >= '2013-12-01'::date) AND (date <= '2014-02-01'::date))
Heap Blocks: exact=1083
-> Bitmap Index Scan on foo_date_idx (cost=0.00..21.06 rows=863 width=0) (actual time=0.208..0.208 rows=1226 loops=1)
Index Cond: ((date >= '2013-12-01'::date) AND (date <= '2014-02-01'::date))
Planning time: 0.104 ms
Execution time: 1.727 ms
(7 rows)
将其与组合方法进行比较
EXPLAIN ANALYZE
SELECT *
FROM foo
WHERE year = 2013 AND month = 12
OR ( year = 2014 AND ( month = 1 OR month = 2) );
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on foo (cost=38.79..2999.66 rows=1203 width=14) (actual time=0.664..2.291 rows=1226 loops=1)
Recheck Cond: (((year = 2013) AND (month = 12)) OR (((year = 2014) AND (month = 1)) OR ((year = 2014) AND (month = 2))))
Heap Blocks: exact=1083
-> BitmapOr (cost=38.79..38.79 rows=1237 width=0) (actual time=0.479..0.479 rows=0 loops=1)
-> Bitmap Index Scan on foo_year_month_idx (cost=0.00..12.64 rows=421 width=0) (actual time=0.112..0.112 rows=402 loops=1)
Index Cond: ((year = 2013) AND (month = 12))
-> BitmapOr (cost=25.60..25.60 rows=816 width=0) (actual time=0.218..0.218 rows=0 loops=1)
-> Bitmap Index Scan on foo_year_month_idx (cost=0.00..12.62 rows=420 width=0) (actual time=0.108..0.108 rows=423 loops=1)
Index Cond: ((year = 2014) AND (month = 1))
-> Bitmap Index Scan on foo_year_month_idx (cost=0.00..12.38 rows=395 width=0) (actual time=0.108..0.108 rows=401 loops=1)
Index Cond: ((year = 2014) AND (month = 2))
Planning time: 0.256 ms
Execution time: 2.421 ms
(13 rows)
它既慢,又丑陋。
GROUP BY
/ORDER BY
组合方法
EXPLAIN ANALYZE
SELECT date, count(*)
FROM foo
GROUP BY date
ORDER BY date;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Sort (cost=20564.75..20570.75 rows=2400 width=4) (actual time=286.749..286.841 rows=2400 loops=1)
Sort Key: date
Sort Method: quicksort Memory: 209kB
-> HashAggregate (cost=20406.00..20430.00 rows=2400 width=4) (actual time=285.978..286.301 rows=2400 loops=1)
Group Key: date
-> Seq Scan on foo (cost=0.00..15406.00 rows=1000000 width=4) (actual time=0.012..70.582 rows=1000000 loops=1)
Planning time: 0.094 ms
Execution time: 286.971 ms
(8 rows)
再用复合法
EXPLAIN ANALYZE
SELECT year, month, count(*)
FROM foo
GROUP BY year, month
ORDER BY year, month;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Sort (cost=23064.75..23070.75 rows=2400 width=4) (actual time=336.826..336.908 rows=2400 loops=1)
Sort Key: year, month
Sort Method: quicksort Memory: 209kB
-> HashAggregate (cost=22906.00..22930.00 rows=2400 width=4) (actual time=335.757..336.060 rows=2400 loops=1)
Group Key: year, month
-> Seq Scan on foo (cost=0.00..15406.00 rows=1000000 width=4) (actual time=0.010..70.468 rows=1000000 loops=1)
Planning time: 0.098 ms
Execution time: 337.027 ms
(8 rows)
通常,让聪明的人去努力。约会很难,我的客户付给我的钱不够。我曾经做过这些测试。我很难得出结论:我能得到比更好的结果date
。我停止尝试了。
建议我在一个月内进行测试@a_horse_with_no_name WHERE (year, month) between (2013, 12) and (2014,2)
。我认为,虽然很酷,但这是一个更复杂的查询,我希望避免这样做,除非有所收获。las,虽然它接近了,但速度仍然较慢-这是该测试的更多收获。根本没关系。
EXPLAIN ANALYZE
SELECT *
FROM foo
WHERE (year, month) between (2013, 12) and (2014,2);
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on foo (cost=5287.16..15670.20 rows=248852 width=14) (actual time=0.753..2.157 rows=1226 loops=1)
Recheck Cond: ((ROW(year, month) >= ROW(2013, 12)) AND (ROW(year, month) <= ROW(2014, 2)))
Heap Blocks: exact=1083
-> Bitmap Index Scan on foo_year_month_idx (cost=0.00..5224.95 rows=248852 width=0) (actual time=0.550..0.550 rows=1226 loops=1)
Index Cond: ((ROW(year, month) >= ROW(2013, 12)) AND (ROW(year, month) <= ROW(2014, 2)))
Planning time: 0.099 ms
Execution time: 2.249 ms
(7 rows)
date
在大多数情况下,这是必经之路。
作为Evan Carroll建议的方法的替代方法,我认为这可能是最好的选择,我在某些情况下(并且不是在使用PostgreSQL时特别使用)仅使用了一个year_month
类型为INTEGER
4字节的列,计算方式为
year_month = year * 100 + month
就是说,您在整数的最右边的两个十进制数字(数字0和数字1)上编码月份,并在数字2到5(或更多,如果需要)上编码年份。
在某种程度上,这是穷人的替代选择,以建立自己的year_month
类型和运算符。与具有两个单独的列相比,它具有一些优点,主要是“意图明确”,并且节省了一些空间(我认为在PostgreSQL中不这样),并且还带来了一些不便。
您只需添加一个即可保证值有效
CHECK ((year_date % 100) BETWEEN 1 AND 12) /* % = modulus operator */
您可以有一个WHERE
类似的子句:
year_month BETWEEN 201610 and 201702
并且有效地工作(year_month
当然,如果该列已正确索引)。
您可以year_month
使用与日期相同的方式进行分组,并且效率相同(至少)。
如果需要将year
和分开month
,则计算很简单:
month = year_month % 100 -- % is modulus operator
year = year_month / 100 -- / is integer division
不方便之处:如果您要在s 中增加15个月,则year_month
必须进行计算(如果我没有犯错误或疏忽):
year_month + delta (months) = ...
/* intermediate calculations */
year = year_month/100 + delta/12 /* years we had + new years */
+ (year_month % 100 + delta%12) / 12 /* extra months make 1 more year? */
month = ((year_month%10) + (delta%12) - 1) % 12 + 1
/* final result */
... = year * 100 + month
如果不小心,可能会容易出错。
如果要获取两个year_months之间的月份数,则需要进行一些类似的计算。这就是(有很多简化)日期算术真正发生的事情,幸运的是,通过已经定义的函数和运算符对我们隐藏了这种情况。
如果您需要大量此类操作,则使用year_month
不太实用。如果您不这样做,那么这是一种明确意图的非常清晰的方法。
作为替代方案,你可以定义一个year_month
类型,定义一个操作year_month
+ interval
,同时还另year_month
- year_month
...和隐藏的计算。实际上,我从来没有像在实践中那样感到需要。一个date
- date
实际上是隐藏你类似的东西。
作为joanolo的method =的替代方法(很抱歉,我很忙,但想写这个)
我们将做同样的事情,但要有一点点。一个int4
在PostgreSQL是一个有符号整数,范围从-2147483648到+2147483647
这是我们结构的概述。
bit
----------------------------------
YYYYYYYYYYYYYYYYYYYYYYYYYYYYMMMM
储存月份。
pow(2,4)
是4位。这是我们存储月份的位图。
bit
----------------------------------
00000000000000000000000000001111
1月-1月-12月12日
bit
----------------------------------
00000000000000000000000000000001
bit
----------------------------------
00000000000000000000000000001100
年份。剩下的28位使我们可以存储我们的年度信息
SELECT (pow(2,28)-1)::int;
int4
-----------
268435455
(1 row)
在这一点上,我们需要决定我们要如何做。出于我们的目的,我们可以使用静态偏移,如果我们只需要覆盖5,000 AD,就可以返回到268,430,455 BC
其中,其中大部分覆盖了中生代的全部内容以及所有有益的发展。
SELECT (pow(2,28)-1)::int4::bit(32) << 4;
year
----------------------------------
11111111111111111111111111110000
而且,现在我们拥有起步的原型,将在2700年后失效。
因此,让我们开始做一些功能。
CREATE DOMAIN year_month AS int4;
CREATE OR REPLACE FUNCTION to_year_month (cstring text)
RETURNS year_month
AS $$
SELECT (
( ((date[1]::int4 - 5000) * -1)::bit(32) << 4 )
| date[2]::int4::bit(32)
)::year_month
FROM regexp_split_to_array(cstring,'-(?=\d{1,2}$)')
AS t(date)
$$
LANGUAGE sql
IMMUTABLE;
CREATE OR REPLACE FUNCTION year_month_to_text (ym year_month)
RETURNS text
AS $$
SELECT ((ym::bit(32) >>4)::int4 * -1 + 5000)::text ||
'-' ||
(ym::bit(32) <<28 >>28)::int4::text
$$ LANGUAGE sql
IMMUTABLE;
快速测试显示了此工作方式。
SELECT year_month_to_text( to_year_month('2014-12') );
SELECT year_month_to_text( to_year_month('-5000-10') );
SELECT year_month_to_text( to_year_month('-8000-10') );
SELECT year_month_to_text( to_year_month('-84398-10') );
现在,我们有了可以在二进制类型上使用的函数。
我们可以从已签名部分中再剪掉一点,将年份存储为正数,然后自然地将其作为已签名整数进行排序。如果速度比存储空间具有更高的优先级,那我们就应该走这条路。但是现在,我们有一个约会与中生代有关。
我可能稍后会更新,只是为了好玩。
month
包含两个整数的数据类型。但我认为,如果您永远不需要,那么每月使用两个整数可能会更容易