Code Metaphor

Programming, Writing, Reading, Thoughts…

이 블로그는 더이상 운영되고 있지 않습니다. 단지 예전 URL을 유지하는 용도로만 남아 있습니다. 새 블로그의 주소는 blog.dahlia.kr입니다.

Archive for November, 2008

A보다 B가 낫다

Thursday, November 27th, 2008

(아래 글에서 “낫다”라는 표현은 해당 도메인에서 낫다는 뜻이지, 당연히 총체적으로 사람이 낫다는 뜻이 아니다. 그런 비교가 설마 이런 식으로 가능하겠는가.)

Python 프로그래머가 Java 프로그래머보다 대체로 더 낫다.1 “과연 그럴까?”라고 생각할 수도 있다. 어쨌든 Python 프로그래머의 대부분은 다언어주의자이기 때문에 Java를 비롯해 다른 다양한 언어들을 함께 사용할 수 있다. 그러나 Java 프로그래머의 대부분은 Python을 모른다.

<table> 태그를 남발하며 그리드 형식의 웹 개발을 하는 사람보다, 웹 표준을 준수하는 사람이 더 낫다. 이것 역시 마찬가지다. 웹 표준을 준수하는 사람도 <table> 태그를 왕창 써가며 그리드 형식의 웹 개발을 할 줄 안다. 그것도 능숙하게. 하지만 “웹 표준은 낭비다”라고 떠들고 다니며 자기합리화하는 사람들은 죄다 웹 표준 개발 제대로 못한다(아니, 정확히 말하면… 대부분 그냥 아예 못한다). 혹은 그러면서 자기가 충분히 경험해봤다고 생각하기까지 한다.

Windows만 써본 사람보다 Mac OS, Linux 등 다른 운영체제를 주로 쓰는 사람이 더 낫다. 후자는 이미 Windows 역시 잘 쓴다. 전자는 써보지도 않았으면서 다른 운영체제는 뭐가 어떻다 말만 많다.

위에서 이야기한 A보다 B가 낫다…라고 하는 케이스들은 모두 하나의 공통점을 가지고 있다. 사실상 A의 경우는 살아가는데(?) 필수적이거나 꼭 거칠 수밖에 없는 케이스고, B는 모두 선택적(optional)이다. 그래서 “대체로 낫다”라는 판단을 일반적으로 수월히 할 수 있다. 선택적인 B를 자처해서 하는 사람은 A를 이미 능숙하게 하는 사람들일 가능성이 높다. 사람들은 A만으로도 감지덕지하는데 B를 자처해서 경험한다는 것은 더 열정있거나 더 적극적이라거나 하는 성향을 추측하게 한다. B에 해당하는 사람은 A를 더러워서 싫어할 가능성이 높지만, A에 해당하는 사람이 B를 싫어한다고 하면 사실은 B에 대해 무지하고 두려워하기 때문이다. 때로 A는 B에 대해 무관심하다. 자기가 아는 영역이 그만큼이기 때문이다.2 사람과 그 집단에 대해 일반화를 한다는 것은 위험한 일이지만, 또 이런 식으로 일반화된 판단을 하는 것이 얼마나 오차가 적냐를 따져보면 대충 그럴듯한 통찰이 아닐까.


  1. Paul Graham, The Python Paradox 

  2. Paul Graham, Beating the Averages 

화두와 느낌들 (2008년 10월, 11월)

Thursday, November 20th, 2008

이 글은 홍민희님의 2008년 10월 19일에서 2008년 11월 20일까지의 미투데이 내용입니다.

PostgreSQL에서 interval을 원하는 resolution의 정수로 가져오기

Thursday, November 13th, 2008

제목이 긴데, 한마디로 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;

구현 상의 실수가 있거나, 좀더 개선될 수 있는 부분이 있으면 코멘트로 남겨주거나, 개인적으로 연락해주시길 바란다!


  1. 자세히 보면 4달을 둘로 나누어 30 × 2와 31 × 2를 더하는 식이다. 당연히 interval은 특정 시각 정보를 가지고 있지 않기 때문에 저렇게밖에 할 방법이 없다. 

  2. 원래는 VLAAH의 친구들 페이지에서 친구들이 쓴 코멘트들 사이의 작성 시각 interval들의 표준 편차를 구하려는 것이었는데, 실제로 초 단위까지는 필요 없고 분 정도의 정보만 알면 되었다. 게다가 두 코멘트 사이의 작성 시각 차이가 한 달이, 1년씩이나 되는 경우가 드물 거라고 판단했다. 그리고 이렇게 단위가 커질 경우 하루 이틀 정도의 오차가 별 의미가 없어진다. (내가 작성하려는 프로그램의 용도에 따르면.) 아, interval을 굳이 정수형으로 구하려고 했던 것은 stddev(standard deviation) 집계 함수(aggregate function)가 숫자만 받기 때문이다. 

PostgreSQL에서 Unique Constraints는 상속되지 않는다

Friday, November 7th, 2008

PostgreSQL에서 지원하는 재미있는 기능 중에 하나가 바로 테이블 상속(table inheritance)이다. 허나 PostgreSQL의 테이블 상속 기능은 역사적으로 오래되었고, 근근히 유지되는 터라 제한사항이 좀 있으니…

A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children.

아무튼 테이블 상속을 쓰는데 unique constraints가 필요해서 추가했다가, 하위 테이블에는 적용되지 않는 것을 보고 빡쳤다 조금 당황했다. 그래서 매뉴얼을 찾아보니 저렇다더라. 어쨌든 그래도 PostgreSQL은 유연한 ORDBMS다. 원하는 만큼 DB 프로그래밍이 가능하다.

어차피 PostgreSQL에서 컬럼 제약 조건(constraints)이 상속되지 않는다는 것은 unique constraints와 외래 키 참조(foreign key reference) 뿐이고, 일반적인 것은 모두 된다. 그러니까, 직접 만들어서 쓰면 된다는 뜻이다.

CREATE LANGUAGE plpgsql;

CREATE FUNCTION topic_is_unique(an_id bigint, a_name text)
RETURNS boolean AS $$
    BEGIN
        RETURN (SELECT count(*) FROM topic WHERE name = a_name AND id != an_id LIMIT 1) < 1;
    END;
$$ LANGUAGE plpgsql;

CREATE TABLE topic (
    id bigserial,
    name text NOT NULL CHECK (length(trim(name)) > 0),
    CONSTRAINT uniqueness_of_name CHECK (topic_is_unique(id, name))
);

CREATE TABLE person (
    nick text NOT NULL CHECK (length(trim(nick)) > 0),
    created_at timestamp with time zone DEFAULT (now())
) INHERITS (topic);

topic 테이블에 추가한 uniqueness_of_name 제약 조건은 person 테이블에도 정상적으로 상속된다. 따라서 아래와 같이 의도한대로 작동하게 된다.

dahlia=# INSERT INTO topic (name) VALUES ('~dahlia');
INSERT 0 1
dahlia=# INSERT INTO person (name, nick) VALUES ('~dahlia', 'MinHee');
ERROR:  new row for relation "person" violates check constraint "uniqueness_of_name"
dahlia=# INSERT INTO person (name, nick) VALUES ('~someone', 'Someone');
INSERT 0 1

실제로 테이블에는 레코드가 아래와 같이 삽입된다.

dahlia=# SELECT * FROM topic;
 id |   name
----+----------
  1 | ~dahlia
  3 | ~someone
(2 rows)

dahlia=# SELECT * FROM person;
 id |   name   |  nick   |          created_at
----+----------+---------+-------------------------------
  3 | ~someone | Someone | 2008-11-07 16:49:06.509321+09
(1 row)

Powered by WordPress. Styled by Hong, MinHee. XML Feed, Comments XML Feed.