별도의 월 및 연도 열 또는 날짜가 항상 1로 설정되어 있습니까?


15

나는에 의해 사물의 그룹화를 많이있을거야 포스트 그레스와 데이터베이스 짓고 있어요 monthyear에 의해,하지만 결코를 date.

  • 정수 monthyear열을 만들어서 사용할 수 있습니다 .
  • 또는 month_year열을 가질 수 있고 항상 day1을 설정했습니다 .

전자는 누군가가 데이터를보고 있으면 조금 더 간단하고 명확 해 보이지만 후자는 올바른 유형을 사용한다는 점에서 좋습니다.


1
또는 month두 개의 정수를 포함하는 고유 한 데이터 형식 을 만들 수 있습니다 . 당신이 지금까지 두 개의 정수를 사용하여, 그 달의 날짜를 필요가 없습니다하지만 내 생각은 아마 쉽게
a_horse_with_no_name

1
가능한 날짜 범위, 가능한 행 수, 최적화하려는 항목 (스토리지, 성능, 안전성, 단순성) 및 (항상) Postgres 버전을 선언해야합니다.
Erwin Brandstetter

답변:


17

개인적으로 날짜이거나 날짜가 될 수 있다면 항상 날짜로 저장하는 것이 좋습니다. 경험상 일반적으로 작업하기가 더 쉽습니다.

  • 날짜는 4 바이트입니다.
  • smallint는 2 바이트입니다 (2 개 필요).
    • ... 2 바이트 : 1 년 동안 smallint 1 개
    • ... 2 바이트 : 한 달 동안 smallint 1 개

필요한 경우 날짜를 지원하는 날짜 또는 smallint추가 정밀도를 절대 지원하지 않는 연도 및 월 날짜를 지정할 수 있습니다 .

샘플 데이터

이제 예제를 보도록하겠습니다. 샘플에 대해 백만 개의 날짜를 만들어 봅시다. 이것은 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)

결론

일반적으로 똑똑한 사람들이 열심히 일하도록하십시오. Datemath는 어렵다, 내 고객은 충분히 지불하지 않습니다. 나는 이런 테스트를했었다. 보다 나은 결과를 얻을 수 있다고 결론을 내릴 수 없었습니다 date. 나는 노력을 멈췄다.

업데이트

한 달 내 테스트를 위해 @a_horse_with_no_name을 제안했습니다 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 ( use-the-index-luke.com/blog/2013-07/…의 45 페이지 참조)와 달리 Postgres는 행 값을 사용하여 인덱스 액세스를 완벽하게 지원합니다. stackoverflow.com/a/34291099/939860 그러나 그것은 따로, 나는 전적으로 동의합니다 : date대부분의 경우 갈 길입니다.
Erwin Brandstetter

5

아마도 아마도 최고의 옵션이라고 생각하는 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 

무엇의 불편 : 당신이 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실제로 당신에게 비슷한 숨어있다.


1
나는 이것을 할 또 다른 방법을 썼다 =) 그것을 즐기십시오.
Evan Carroll

방법과 장단점에 감사드립니다.
phunehehe

4

joanolo의 방법 =)에 대한 대안으로 (미안하지만 바빴지만 이것을 쓰고 싶었습니다)

비트 기쁨

우리는 똑같은 일을 할 것입니다. int4PostgreSQL의 하나 는 -2147483648에서 +2147483647 범위의 부호있는 정수입니다.

다음은 구조에 대한 개요입니다.

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

보관 월.

  • 한 달에 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,000 AD 만 268,430,455 BC커버하면 Mesozoic 전체 와 유용한 모든 것을 다룰 수 있습니다.

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') );

이제 이진 형식에 사용할 수있는 함수가 있습니다.

부호있는 부분에서 한 비트를 더 잘라 내고 연도를 양수로 저장 한 다음 자연스럽게 부호있는 정수로 정렬 할 수있었습니다. 스토리지 공간보다 속도가 우선 순위가 높았다면 이것이 우리가 내려간 경로 일 것입니다. 그러나 지금은 중생대와 일할 날짜가 있습니다.

나중에 재미로 업데이트 할 수 있습니다.


범위는 아직 불가능합니다. 나중에 살펴 보겠습니다.
Evan Carroll

"비트에 최적화"는 "저수준 C"에서 모든 기능을 수행 할 때 모든 의미가 있다고 생각합니다. 당신은 최후의 비트와 최후의 나노초까지 저장합니다 ;-) 어쨌든, 즐거운! (여전히 BCD를 기억합니다. 반드시 기쁨이있는 것은 아닙니다.)
joanolo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.