別の月と年の列、または日付が常に1に設定されている日付?


15

私はPostgresを使用してデータベースを構築していますが、ここではmonthand によってグループ化されますが、によってグループ化されることはありyearませんdate

  • 整数monthyear列を作成して使用できます。
  • または、month_year列を作成し、常にday1に設定することもできます。

前者は誰かがデータを見ている場合は少し単純で明確に見えますが、後者は適切な型を使用する点で優れています。


1
または、month2つの整数を含む独自のデータ型を作成できます。あなたは、これまでに2つの整数を使用して、月の日付を必要としたことがない場合でも、私が思うには、おそらく簡単です
a_horse_with_no_name

1
日付の可能な範囲、行の可能な数、最適化しようとしているもの(ストレージ、パフォーマンス、安全性、シンプルさ)および(常に)Postgresのバージョンを宣言する必要があります。
アーウィンブランドステッター

回答:


17

個人的に日付である場合、または日付である場合は、常に1つとして保存することをお勧めします。経験則として作業する方が簡単です。

  • 日付は4バイトです。
  • smallintは2バイトです(2つ必要です)
    • ... 2バイト:1年間に1つの小さい整数
    • ... 2バイト:月に1つの小さい整数

必要に応じて日をサポートする日付を1つ、またはsmallint余分な精度をサポートしない年と月の日付を1つ持つことができます。

サンプルデータ

ここで例を見てみましょう。サンプル用に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ではありません。いくつかは多少多かれ少なかれですが、それは問題ではありません。それらはすべて比較的同じです。単に必要ではありません。

1ヶ月以内

それでは、それを楽しんでみましょう.. 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)

結論

一般的に、賢い人々に一生懸命やらせてください。Datemathは大変で、クライアントは私に十分な支払いをしません。私はこれらのテストを行っていました。私はこれまでよりも良い結果を得ることができると結論を出すのにとても苦しんでいました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)

4
他のいくつかのRDBMS(のページを参照してください45とは異なりuse-the-index-luke.com/blog/2013-07/...)、Postgresは、完全にインデックス行の値を使用してアクセスをサポートします。stackoverflow.com/a/34291099/939860をしかし、それはです余談ですが、私は完全に同意しますdate。ほとんどの場合、これが道です。
アーウィンブランドステッター

5

Evan Carrollが提案する方法の代替として、おそらく最良のオプションと考えられますが、(PostgreSQLを使用する場合は特にそうではありません)year_monthINTEGER(4バイト)の列だけを使用して計算しました。

 year_month = year * 100 + month

つまり、次の2つの右端の上の月エンコード小数点(必要に応じて、またはそれ以上)〜5桁2の整数の桁(数字0、および数字1)、および年。

これは、ある程度まで、あなた自身の型と演算子を構築することに対する貧乏人の代替手段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...と計算を非表示にします。私は実際に、実際に必要性を感じるほど重い使用をしたことはありません。A date- date実際にあなたに似た何かを隠しています。


1
私はこれを行う別の方法を書いた=)それを楽しんでください。
エヴァンキャロル

ハウツーと賛否両論に感謝します。
プネヘヘ

4

joanoloのメソッドの代替として=)(申し訳ありませんが忙しかったのですが、これを書きたいと思いました)

ビットジョイ

同じことを少しずつ行います。int4PostgreSQLの1つは、-2147483648〜+2147483647の範囲の符号付き整数です。

構造の概要を以下に示します。

               bit                
----------------------------------
 YYYYYYYYYYYYYYYYYYYYYYYYYYYYMMMM

保管月。

  • 1か月には12 ビットのオプションpow(2,4)4ビット必要です。
  • 残りの年は32-4 = 28ビットです。

ここに、月が格納される場所のビットマップがあります。

               bit                
----------------------------------
 00000000000000000000000000001111

月、1月1日-12月12日

               bit                
----------------------------------
 00000000000000000000000000000001
               bit                
----------------------------------
 00000000000000000000000000001100

年。残りの28ビットにより、年情報を保存できます

SELECT (pow(2,28)-1)::int;
   int4    
-----------
 268435455
(1 row)

この時点で、これをどのように行うかを決定する必要があります。この目的のために、静的オフセットを使用できます。5,000ADのみをカバーする必要がある場合は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') );

これで、バイナリ型で使用できる関数ができました。

署名部分からもう少し切り取り、その年を正として保存し、それを自然に署名整数としてソートすることもできます。速度がストレージスペースよりも優先度が高かった場合、それが我々が下るルートだったでしょう。しかし、今のところ、中生代で機能する日付があります。

楽しみのために、後で更新するかもしれません。


範囲はまだ可能ではありません。後で説明します。
エヴァンキャロル

「低レベルC」ですべての機能を作成する場合、「ビットに最適化」することはすべて理にかなっていると思います。あなたは最後まで最後のビットと最後まで最後のナノ秒を節約します;-)とにかく、楽しい!(私はまだBCDを覚えています。必ずしも喜びとは
限り
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.