象と戯れ

 | 

2008-08-06

13日の金曜日を数え上げる - PostgreSQL編

23:09 | 13日の金曜日を数え上げる - PostgreSQL編 - 象と戯れ を含むブックマーク はてなブックマーク - 13日の金曜日を数え上げる - PostgreSQL編 - 象と戯れ

文字列に含まれる単語の最初の文字を大文字にする - iakioの日記 - postgresqlグループ

にインスパイアされて。

no title

日付処理はPostgreSQLの内部関数を使うとかなりお手軽です。

#include <postgres.h>
#include <fmgr.h>
#include <funcapi.h>
#include <utils/date.h>
#include <utils/datetime.h>

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(dokaku6985);
PG_FUNCTION_INFO_V1(dokaku6985_set);

Datum dokaku6985(PG_FUNCTION_ARGS);
Datum dokaku6985_set(PG_FUNCTION_ARGS);

Datum dokaku6985(PG_FUNCTION_ARGS)
{
    DateADT     dt;
    int       y, m, d, day;
    int4      result = 0;

    if(PG_ARGISNULL(0))
    {
        struct pg_tm  tt, *tm = &tt;
        fsec_t      fsec;
        if(timestamp2tm(GetCurrentTimestamp(), NULL, tm, &fsec, NULL, NULL) != 0)
            elog(ERROR, "error");
        dt = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
    }
    else
    {
        dt =  PG_GETARG_DATEADT(0) + POSTGRES_EPOCH_JDATE;
    }

    do{
        j2date(dt, &y, &m, &d);
        day = j2day(dt);
        dt++;
        if(d == 13 && day == 5)
            result++;
    }while(!(y == 2013 && m == 12 && d == 31));



    PG_RETURN_INT32(result);
}

Datum dokaku6985_set(PG_FUNCTION_ARGS)
{
    FuncCallContext *context;
    DateADT     dt;
    int       y, m, d, day;

    if(SRF_IS_FIRSTCALL())
    {
        context = SRF_FIRSTCALL_INIT();
        if(PG_ARGISNULL(0))
        {
            struct pg_tm  tt, *tm = &tt;
            fsec_t      fsec;
            if(timestamp2tm(GetCurrentTimestamp(), NULL, tm, &fsec, NULL, NULL) != 0)
                elog(ERROR, "error");
            dt = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
        }
        else
        {
            dt =  PG_GETARG_DATEADT(0) + POSTGRES_EPOCH_JDATE;
        }
        context->user_fctx = (void *) dt;
    }

    context = SRF_PERCALL_SETUP();
    dt = (DateADT) context->user_fctx;

    do{
        j2date(dt, &y, &m, &d);
        day = j2day(dt);
        dt++;
        if(d == 13 && day == 5)
        {
            context->user_fctx = (void *) dt;
            SRF_RETURN_NEXT(context, dt - POSTGRES_EPOCH_JDATE - 1);
        }
    }while(!(y == 2013 && m == 12 && d == 31));

    SRF_RETURN_DONE(context);
}

MakefileはPGXSを使うと楽勝。

OBJS = dokaku6985.o
MODULE_big = dokaku
PGXS := $(shell pg_config --pgxs)
include $(PGXS)

関数を作って

CREATE OR REPLACE FUNCTION dokaku6985() RETURNS int4 AS 
'$libdir/dokaku', 'dokaku6985'
LANGUAGE c STRICT;

CREATE OR REPLACE FUNCTION dokaku6985_set() RETURNS SETOF date AS 
'$libdir/dokaku', 'dokaku6985_set'
LANGUAGE c STRICT;

実行!

SELECT dokaku6985();
 dokaku6985 
------------
         10
(1 row)

SELECT dokaku6985_set();
 dokaku6985_set 
----------------
 2009-02-13
 2009-03-13
 2009-11-13
 2010-08-13
 2011-05-13
 2012-01-13
 2012-04-13
 2012-07-13
 2013-09-13
 2013-12-13
(10 rows)

SELECT count(*) FROM dokaku6985_set();
 count 
-------
    10
(1 row)

問題文に20013年って書いてあるように見えて困った。

2013年だよね?

どう書くってどうやって投稿するのかは知りませんが、

まあしかし短くはないですね。


Enjoy PG hacking!

L.starL.star2008/08/07 00:19そんな、関数なんか書かなくても 7.4+pgbenchのaccountsテーブル込みだったら

select count(*) from (select (current_date+aid) as i from accounts where aid <= extract('day' from '2013-12-31'-now())) d where extract (dow from i)=5 and extract(day from i)=13;

ぐらいで書けますよ(手元に8.0以降が無いのでgenerate_seriesをaccountsテーブルで代用:))

それこそgenerate_series()が使えるなら

select count(*) from (select current_date+generate_series(0,extract('day' from '2013-12-31'-now()) as i) i where extract (dow from i)=5 and extract(day from i)=13;

で十分じゃ。intervalとdateの変換を暗黙なのに頼らなければもっと美しくなるかと。

# 今日が13日の金曜日だったら働かなかったりする気がするな

umitanukiumitanuki2008/08/07 10:24ええ、きっとそれが(generate_seriesが)正解ですw。extractの引数に整数って使えるのでしょうか??

実は曜日は 修正ユリウス通日 mod 7 で求められるので、13日というのが修正ユリウス通日から簡単にわかれば日付型すら使う必要がないのですが。。。

iakioiakio2008/08/07 17:02iはcurrent_date + generate_...なので日付型ですね。
extractの戻り値がfloat8なので
select count(*) from (select current_date + generate_series(0, extract('day' from '2013-12-31' - now())::int) as i) i where extract(dow from i) = 5 and extract(day from i) = 13;
で、8.3で動きました。でも8.4devなら
select count(*) from generate_series(current_date, '2013-12-31', '1 day') i where extract(dow from i) = 5 and extract(day from i) = 13;
でOKですね。

umitanukiumitanuki2008/08/07 22:46あ-、そうですね。iが整数型のような勘違いをしてました。

あの、まあ、ネタなので・・・

 |