Postgresを使用してデータベースを構築しています。このデータベースでは、month
とyear
によるグループ化が多数行われますが、date
によるグループ化は行われません。
month
およびyear
列を作成して、それらを使用できます。month_year
列で、常にday
を1に設定します。前者は誰かがデータを見ると少し単純で明確に見えますが、後者は適切なタイプを使用するという点でいいです。
個人的には、日付である場合、または日付である可能性がある場合は、常に日付として保存することをお勧めします。経験則として作業する方が簡単です。
必要に応じて1日をサポートする1つの日付、または余分な精度をサポートしない年と月の1つの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が私の提案1か月以内テストWHERE (year, month) between (2013, 12) and (2014,2)
。私の意見では、クールですが、これはより複雑なクエリであり、利益が得られない限り避けたいと思います。悲しいかな、それは近いですがそれでもまだ遅かったです-これはこのテストからのテイクアウトのより多くです。それは単に大した問題ではありません。
_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)
_
Evan Carrollが提案する方法の代替案として、おそらく最良のオプションを検討し、場合によっては(特にPostgreSQLを使用する場合ではなく)INTEGER
タイプのyear_month
列のみを使用しました( 4バイト)、次のように計算されます
year_month = year * 100 + month
つまり、整数の右端decimalの2桁(数字0、および数字1)で月をエンコードし、数字で年をエンコードします。 2から5(必要な場合はそれ以上)。
これは、独自のyear_month
タイプと演算子を作成する代わりに、ある程度、貧乏人の代替手段です。それはいくつかの利点、主に「意図の明確さ」、およびいくつかのスペースの節約(PostgreSQLではない)、さらに2つの別々の列を持つことによるいくつかの不便さがあります。
値を追加するだけで値が有効であることを保証できます
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
不便とは: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
注意しないと、エラーが発生しやすくなります。
2つのyear_months間の月数を取得する場合は、いくつかの同様の計算を行う必要があります。これは(多くの簡略化により)日付演算の内部で実際に行われることであり、幸運にも、既に定義されている関数や演算子によって隠されています。
これらの操作が多数必要な場合は、year_month
を使用するのはあまり現実的ではありません。そうでない場合は、意図を明確にする非常に明確な方法です。
別の方法として、year_month
タイプを定義し、演算子year_month
+ interval
を定義し、さらに別のyear_month
-year_month
...および計算を非表示にします。私は実際には、実際に必要性を感じるほどの重い使用はしていません。 date
-date
は、実際には似たようなものを隠しています。
Joanoloの方法の代替として=)(申し訳ありませんが、忙しかったが、これを書きたかった)
同じことをしますが、ビットを使用します。 PostgreSQLの_int4
_の1つは、-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)
_
この時点で、これを実行する方法を決定する必要があります。この目的のために、静的オフセットを使用できます。AD5,000をカバーするだけでよい場合は、 中生代 全体をほぼカバーする_268,430,455 BC
_に戻ることができます。前進に役立つすべてのもの。
_SELECT (pow(2,28)-1)::int4::bit(32) << 4;
year
----------------------------------
11111111111111111111111111110000
_
そして今、私たちのタイプの原始があり、2、700年で期限切れになる予定です。
それでは、いくつかの関数の作成に取り掛かりましょう。
_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') );
_
これで、バイナリ型で使用できる関数ができました。
署名された部分からもう1つ切り取り、年を正として保存し、自然に署名されたintとして並べ替えることができます。速度がストレージスペースよりも優先された場合、それは私たちが下に行くルートでした。しかし、今のところ、中生代で機能する日付があります。
後で面白くするために、後で更新するかもしれません。