Posts Tagged ‘PostgreSQL’

    はてなブックマーク - PostgreSQLで数え年計算
    このエントリーをはてなブックマークに追加

    まさかまさかの4回目。いま宝くじを買えば3億当確の安元です。
    最近肩こりがひどいので、昨日からピップマグネループつけてます。
    そちらのレビューはまた今度。ビバ!プラシーボ!!

    さて本題。
    最近めっきり聞くことも少なくなった「数え年」。皆さんご存じでしょうか?
    ウィキペディアさんに聞いてみました

    数え年(かぞえどし)とは年齢や年数の数え方の一つである。生まれた時点、基点となる最初の年を「1歳」、「1年」とし、以降元日(1月1日)を迎えるごとにそれぞれ1歳、1年ずつ加える(例:12月31日に出生した場合、出生時に1歳で翌日(1月1日)に2歳となる。また1月1日に出生した場合は、2歳になるのは翌年の1月1日になる)。数え歳とも、単に数えともいう。
    これに対し、誕生日前日午後12時に加齢、加年する数え方を満年齢、満という。本稿においては主に年齢に関する事柄について記述する。

    これ日本独自のものかと思ってましたが、アジア圏ではそれなりに使われているそうです。
    私が必要となった数え年は、「1月1日」での年齢加算ではなく
    「4月1日」での年齢加算でした。いかにも日本の企業で必要になりそうですね!

    年齢を計算してみましょう

    誕生日から一般的な年齢計算を行いましょう

    ここで一番大切なのは「1979-09-02」ですね!私の誕生日です!

    SELECT句の2行目
    age関数が返してくる値は経過年月日ですね。
    SELECT句の3行目
    age関数の結果から「年」だけを切り出したものこれが「満年齢」になります。

    1月1日で年齢を加算する

    数え年ということは、「1月1日」で年齢が加算されるということです。
    ということは、何月に生まれようとも誕生日を「1月1日」生まれにしてしまえば
    みんな一緒に歳をとれますね!

    SELECT句2行目が示すように、
    誕生日から年だけ切り出し「-01-01」を付け足すことで無理矢理「1月1日」に強制しています
    やや乱暴ですが、これで数え年はでました。

    4月1日で年齢を加算する

    では日本ではよく使う「4月1日」を基準に考える「年度」で歳を取りましょう。

    ここで問題になるのは1月〜3月生まれの人々です。
    先ほどのように全員「4月1日」生まれに強制してしまいたいのですが、単純に年を切り出すと
    1月〜3月生まれは1歳進んでしまいます。
    この3ヶ月が邪魔なので、みんな誕生日を3ヶ月巻き戻して考えてしまいましょう。
    3月生まれは前年の12月生まれに、4月生まれは同年1月生まれになりますね。
    これで年をそろえることができます。

    1920年8月生まれは90歳 3月生まれは91歳と差が出ているのがわかります。

    PostgreSQLでは、日付計算が「 + ’1months’」のように月単位や年単位での計算ができます。

    長々とSQLを書いてきましたが、システム内で頻繁に年度計算を利用する場合は
    関数化してSQLに組み込むと使い勝手がよくなるのでご検討ください。

    それでは肩がこってきたのでこの辺で。

      はてなブックマーク - PostgreSQLでCase式とGroup化
      このエントリーをはてなブックマークに追加

      まさか3度目は無いだろうと完全に油断していた。。。。
      Advent Calendar3度目の登場の安元です。
      年末ジャンボを買うなら今だ!と無責任にけしかける社員がいっぱいる楽しい会社です。

      さて、前回、前々回とSQLについて書いております。今回も趣向も変えずにSQLです。
      PHPでもRubyでもフレームワークを利用されることがほとんどで
      「SQLなんて書かねーよ!!」というかたも多いのではないでしょうか?

      弊社でもフレームワーク導入の前後ではSQLに携わる機会は激減しているのが現状です。
      しかし業務系のシステムでは、集計や統計を必要とすることがよくありますので、
      「SQLでデータまとめる」という選択肢を持っておくと、プログラムがぐっとシンプルになることもあります。

      というわけで、今回はGroup ByとCase式を絡めて利用してみましょう。

      年齢ごとの集計

      今回利用するのは以下のようなテーブルです
      性別と誕生日が格納されているシンプルなテーブル構成です。
      実務ではユーザーテーブルなんかでよくある構成ですね。
      レコード数は全部で3525件準備しました。

      
       id | sex |  birthday
      ----+-----+------------
        1 | f   | 1920-08-08
        2 | f   | 1927-07-28
        3 | f   | 1920-03-28
        4 | f   | 1921-03-21
        5 | f   | 1913-12-23
      (10 rows)
      

      まずはシンプルに年齢ごとの集計をとってみましょう
      (12月1日時点での年齢をもとに集計します)

      これで「年齢」ごとの集計はできました。
      しかし実務では、「年齢」ごとの集計では情報の粒度が細かすぎることが多いです。

      年代ごとの集計

      では、10代・20代のように年代ごとの集計をしてみましょう。
      使うのは、Case式です。
      まずはSQLを見てみましょう

      特徴的なのは、
      SELECT句の最初のCase式で条件分けをしていることと
      COUNT関数の中でのCase式の利用いる部分ですね

      SELECT句のCase式はGROUP BYにも書くことができますが、
      SELECT句に先に記述し「AS」で別名を付けることで、
      GROUP BY、ORDERともに別名を利用でき、SQLを短くすることができます。

      Case式の中でやっていることは年齢を年代に置き換える作業です

      date_part('year', age('2010-12-1',birthday)) between 1 and 19 then 10

      分解してみましょう
      1:age関数を利用して、誕生日からの経過年・月・日が取得できます

      age('2010-12-1',birthday)

      2:取得した年月日から、年だけを取得します。これが年齢になりますね

      date_part('year', age('2010-12-1',birthday))

      3:最後にbetweenを利用して、「年代」に置き換えています

      case
      when date_part('year', age('2010-12-1',birthday)) between 1 and 19 then 10
      end

      このようにCase式を使うと、「条件に一致する複数のデータを一つのデータにまとめ直す」ことができるのが大きな利点です。

      性別・年代ごとの集計

      それでは最後に、先ほどの年代別の集計を性別ごとに分けてみましょう。
      問い合わせ結果が横に伸びるということは、SELECT句の項目が増えることになります。
      今回はシンプルにCase式の各条件に性別条件を付加しました

      結果はこのようになります

      age_group | men | women
      -----------+-----+-------
      20 | 0 | 1
      30 | 14 | 192
      40 | 45 | 663
      50 | 56 | 905
      60 | 47 | 743
      70 | 9 | 367
      80 | 21 | 278
      90 | 4 | 141
      100 | 1 | 36
      (9 rows)

      とてもシンプルな結果が返ってきました。
      Case式は応用の幅が広いです。
      思いもつかなかった活用法がWEB紹介されていることもありますから、
      集計に行き詰ったら、Case式の活用を検討してみるといいことがあるかもしれません。

        はてなブックマーク - PostgreSQLでクロス集計! tablefunc crosstab
        このエントリーをはてなブックマークに追加

        皆様こんにちは、
        新婚初日のウィークdayから午前帰宅で新妻にメールで泣かれた安元です。

        結婚式の前後でAdvent Calendarに当たるなんて、
        きっと日頃の行いがいいからだと思います!
        早くも2回目になっているのは気のせいです。

        閑話休題

        本日のお題はクロス集計です。
        SQLを実行した結果は、基本的に下に々伸びるものです。
        あなたも思ったことがあるのでは無いでしょうか?
        「SQLの結果が横に伸びれば。。。。」
        そうなんです、SQLの結果を横に伸ばすことが出来れば、クロス集計が行えるんです。
        今日はそんなお話です。

        まずはサクッとyumでpostgresql-contribをインストールしましょう
        このpostgresql-contribには、postgreSQLをいろいろと便利にしてくれる関数などのSQLが格納されています

        yum install postgresql-contrib

        インストールが完了したら、以下のコマンドでDBに関数を生成します
        postgrsql-contribの設置場所は環境に合わせて変更してください。

        psql -U postgres -f /usr/share/doc/postgresql-contrib-8.4.1/tablefunc.sql dbname 
        SET
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE TYPE
        CREATE TYPE
        CREATE TYPE
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE FUNCTION
        CREATE FUNCTION

        さて準備がととのいました。
        pgAdminなどでDBを確認してみてください。関数などが増えているはずです。

        今回使用するのは以下のようなデータです。
        ID|氏名|性別|登録日
        12/1~12/5までに登録があったユーザーと性別が登録されています。
        このデータから、日付と性別でクロス集計して見ましょう。

        
         id |  name   | sex |       created
        ----+---------+-----+---------------------
         1 | USER001 | f   | 2010-12-01 00:00:00
         2 | USER002 | t   | 2010-12-05 00:00:00
         3 | USER003 | t   | 2010-12-01 00:00:00
         4 | USER004 | t   | 2010-12-02 00:00:00
         5 | USER005 | f   | 2010-12-02 00:00:00
         6 | USER006 | t   | 2010-12-04 00:00:00
         7 | USER007 | t   | 2010-12-05 00:00:00
         8 | USER008 | f   | 2010-12-04 00:00:00
         9 | USER009 | f   | 2010-12-04 00:00:00
        10 | USER010 | t   | 2010-12-01 00:00:00
        11 | USER011 | f   | 2010-12-04 00:00:00
        12 | USER012 | t   | 2010-12-04 00:00:00
        13 | USER013 | f   | 2010-12-04 00:00:00
        14 | USER014 | t   | 2010-12-05 00:00:00
        15 | USER015 | t   | 2010-12-02 00:00:00
        16 | USER016 | t   | 2010-12-05 00:00:00
        17 | USER017 | f   | 2010-12-02 00:00:00
        18 | USER018 | t   | 2010-12-01 00:00:00
        19 | USER019 | t   | 2010-12-02 00:00:00
        20 | USER020 | t   | 2010-12-05 00:00:00
        (20 rows)
        

        ①GROUP BYによる日付ごと、性別ごとの人数を集計

        集計といえば、GROUP BYとCOUNT()ですね。

        
        SELECT
        	created,
        	sex,
        	count(id)
        FROM
        	customer
        GROUP BY
        	created,
        	sex
        ORDER BY
        	created,
        	sex ;
               created       | sex | count
        ---------------------+-----+-------
         2010-12-01 00:00:00 | f   |     1
         2010-12-01 00:00:00 | t   |     3
         2010-12-02 00:00:00 | f   |     2
         2010-12-02 00:00:00 | t   |     3
         2010-12-04 00:00:00 | f   |     4
         2010-12-04 00:00:00 | t   |     2
         2010-12-05 00:00:00 | t   |     5
        (7 rows)
        

        ②crosstab関数の利用

        ①で生成したSQLをcrosstab関数に投げ込みます

        
        SELECT
        	*
        FROM
        	crosstab(
                        -- ここから①のSQL
                        'SELECT
                                created,
                                sex,
                                count(id)
                         FROM
                                customer
                        GROUP BY
                                created,
                                sex
                       ORDER BY
                               created,
                               sex'
                        -- ここまで
        ) AS customer(
                -- 最終的に出力するデータの型を指定します
                days timestamp,
                male bigint,
                female bigint
        );
        
                days         | male | female
        ---------------------+------+--------
         2010-12-01 00:00:00 |    1 |      3
         2010-12-02 00:00:00 |    2 |      3
         2010-12-04 00:00:00 |    4 |      2
         2010-12-05 00:00:00 |    5 |
        (4 rows)
        

        さあ、これだけで、縦に日付、横に性別をもったクロス集計が出来上がっています!

        SELECT * FROM crosstab('GROUP化SQL') AS (結果表のデータ型指定)

        たったこれだけで、あとはPHPなりRubyなりでぐるっとforeachでも回せば、
        集計表の出力が行えます。
        ただのグループ化よりも、はるかに簡潔なプログラムになることうけあいです!

        余談ですが、このままでは12/3日の集計が出ていませんね。。。
        12/3はだれも入会しなかったのでしょう。データないものはGROUP BYでも集計できません。
        先日ご紹介したgenerate_seriesも合わせて利用して見ましょう

        
        SELECT
        	days,
        	coalesce(F,0) AS male,
        	coalesce(T,0) AS female
        FROM
        	crosstab(
        'SELECT
        	days,
        	sex,
        	count(id)
        FROM
        (
        	SELECT
        		date(''2010-12-1'') + a::int as days
        	FROM
        		generate_series(0,4) as a
        ) as generate
        LEFT OUTER JOIN
        	customer
        ON
        	(generate.days = customer.created)
        GROUP BY 
        	days,
        	sex
        ORDER BY 
        	days,
        	sex
        '
        ) AS customer(
        	days date,
        	F bigint,
        	T bigint
        );
        
            days    | male | female
        ------------+------+--------
         2010-12-01 |    1 |      3
         2010-12-02 |    2 |      3
         2010-12-03 |    0 |      0
         2010-12-04 |    4 |      2
         2010-12-05 |    5 |      0
        (5 rows)
        

        これで3日と5日の「0」も集計できました。
        FOR文やIF分の分岐が多い、またネストが多くなる場面で使用すると
        驚くほどの速度向上が見込めることがありますので、おためしあれ。