PostgreSQL에서 interval을 원하는 resolution의 정수로 가져오기
제목이 긴데, 한마디로 interval '3 years 4 months 8 days 13 hours 27 min 36 sec'에 대해 저게 초로 환산하면 대체 몇초인지
106964856 = (60 × ((60 × (24 × (((365 × 3 = 1095) + (30 × 2 = 60) + (31 × 2 = 62)1 + 8) + 13 = 1238) = 29712) = 1782720) + 27 = 1782747) = 106964820) + 36
이런 식으로 구하고 싶다는 것이다. 비슷하게 앞과 같은 interval 값에 대해 날자수로 환산하면 대체 몇일이나 되는지는
1238 = ((365 × 3 = 1095) + (30 × 2 = 60) + (31 × 2 = 62) + 8) + 13
이런 식으로 구한다. 그러니까 정리하자면, 년, 월, 일, 시, 분, 초 가운데 원하는 시간 단위의 정수로 interval을 환산하고 싶다. PostgreSQL에 extract라는 기능이 있긴 하지만 우리가 원하는 그런 기능은 아니다.
dahlia=# select extract(year from interval '1 year');
date_part
-----------
1
(1 row)
dahlia=# select extract(year from interval '1 year 2 month');
date_part
-----------
1
(1 row)
dahlia=# select extract(month from interval '1 year 2 month');
date_part
-----------
2
(1 row)
보다시피 extract는 해당 단위의 정수를 가져오긴 하지만(정확히는 float), 해당 단위보다 큰 단위를 합쳐서 가져오진 않는다. 마지막 표현식에 대해 우리가 원하는 값은 2가 아니라 14다.
처음에는 PostgreSQL에 왜 저런 거 해주는 함수 하나 없냐고 투덜대며 직접 함수를 만들기 시작했는데, 만들다보니 왜 그런지 알 것 같아졌다. 사실 interval은 시각 정보를 담고 있지 않기 때문에, 정확히 다른 단위로 환산하기가 매우 힘들다. 그래서 PostgreSQL은 interval '1 year'와 interval '365 days'를 구분한다. 전자의 1년이 윤년인지 아닌지 알 수 없기 때문이다. 마찬가지로 interval '2 months'를 일수로 환산하기는 너무 모호하다. 저 두달이라는 것이 7월과 8월이면 62일이 되겠으나 1월과 2월이라면 그 해가 윤년이냐 아니냐에 따라 또 달라지게 된다.
하지만 내가 해당 interval을 특정 단위로 환산하고자 하는 것은 정확한 값이 아니라 근사값을 원하는 것이었기 때문에 내가 만들고자 하는 프로그램에서 허용할 수 있을만한 오차는 무시하고 적당히 실용적인 관점에서 구현해보았다.2 구현을 보면 하나도 어려운 것이 없으며, 윤년이나 2월에 대해서는 처리를 하지 않았거나 대충 넘어갔다;;;
Create Or Replace Function Interval_getMonths(v interval)
Returns bigint AS $$
Begin
Return extract(month From v) + 12 * extract(year From v);
End;
$$ Language plpgsql;
Create Or Replace Function Interval_getDays(v interval)
Returns bigint AS $$
Begin
Return extract(day From v) + floor(
30 * extract(month From v) * 5 / 12.0 + -- months of 30 days
31 * extract(month From v) * 7 / 12.0 -- months of 31 days
)
+ 365 * extract(year From v)
+ floor(extract(year From v) / 4); -- leap years (not exactly)
End;
$$ Language plpgsql;
Create Or Replace Function Interval_getHours(v interval)
Returns bigint AS $$
Begin
Return extract(hour From v) + 24 * Interval_getDays(v);
End;
$$ Language plpgsql;
Create Or Replace Function Interval_getMinutes(v interval)
Returns bigint AS $$
Begin
Return extract(minute From v) + 60 * Interval_getHours(v);
End;
$$ Language plpgsql;
Create Or Replace Function Interval_getSeconds(v interval)
Returns bigint AS $$
Begin
Return extract(second From v) + 60 * Interval_getMinutes(v);
End;
$$ Language plpgsql;
구현 상의 실수가 있거나, 좀더 개선될 수 있는 부분이 있으면 코멘트로 남겨주거나, 개인적으로 연락해주시길 바란다!
-
자세히 보면 4달을 둘로 나누어 30 × 2와 31 × 2를 더하는 식이다. 당연히
interval은 특정 시각 정보를 가지고 있지 않기 때문에 저렇게밖에 할 방법이 없다. ↩ -
원래는 VLAAH의 친구들 페이지에서 친구들이 쓴 코멘트들 사이의 작성 시각
interval들의 표준 편차를 구하려는 것이었는데, 실제로 초 단위까지는 필요 없고 분 정도의 정보만 알면 되었다. 게다가 두 코멘트 사이의 작성 시각 차이가 한 달이, 1년씩이나 되는 경우가 드물 거라고 판단했다. 그리고 이렇게 단위가 커질 경우 하루 이틀 정도의 오차가 별 의미가 없어진다. (내가 작성하려는 프로그램의 용도에 따르면.) 아,interval을 굳이 정수형으로 구하려고 했던 것은stddev(standard deviation) 집계 함수(aggregate function)가 숫자만 받기 때문이다. ↩
