SQL(Postgresql)で曜日や週で集計する
ふと、Postgresqlで曜日や週の集計ができんのかなーと思って、検索してみたところ、あった。過去にも何度も思ったことがあるはずだが)、探し方が悪かったのだろう。postgresql7.2のころから、extract関数というのがあって日付から世紀 century や日 day や十年 decade やら曜日 dow がとりだせる。「extract 関数は日付/時刻の値から年とか時刻などの部分フィールドを抽出します」だそうですよ!(がーん! 知らなかった。。。)詳細は「日付 関数 演算子」などで、postgresqlのドキュメントで探してください。バージョンにはご注意。。
曜日をとりだす例。
select extract(dow from timestamp '2001-02-16 20:38:40');
この場合、 '2001-02-16 20:38:40'をtimestampにして、そこからDOWで曜日をとりだしている。どぅ? どゅ?
勉強用のテーブルをつくる。idはキーがいるかなと思って設定した。注文日と数量なやつ。(英語へのツッコミは慈悲の心をもって。。。)
CREATE TABLE orders ( id serial NOT NULL, order_date timestamp with time zone, amount integer, CONSTRAINT orders_pkey PRIMARY KEY (id) )
order_dateの曜日を求める。
select order_date ,extract(dow from order_date) ,amount from orders
すると、曜日が、0〜6(日曜日が 0)で求めることができる。(曜日があっているかどうかはカレンダーを見よ!)
ここでぐいっと日本語の曜日で表示したい場合
select order_date ,extract(dow from order_date) ,(array['日','月','火','水','木','金','土'])[extract(dow from order_date) + 1] ,amount from orders order by order_date
など。配列をつくって何番目か、という塩梅なのだが、ARRAYは添字が1かららしいので、プラス1している。
ほぉほぉ。
これは
SELECT (ARRAY['日','月','火','水','木','金','土'])[EXTRACT(DOW FROM CAST('2012/01/25' AS DATE)) + 1]
を参考にしたのだけど、このサイトをどこでみたか忘れてしまった。。すいませぬ。。。
曜日ごとに集計する。
それっぽくするために日付の条件を追加。
select extract(dow from order_date) as day_of_the_week ,(array['日','月','火','水','木','金','土'])[extract(dow from order_date) + 1] ,sum(amount) from orders where order_date between '2011/11/01' and '2011/12/01' group by day_of_the_week order by day_of_the_week
これで、曜日ごとの集計ができる。
次は週で集計。
参考:ログイン - yanor.net/wiki/ありがたや。。。
まずは、週のはじめを決める。日曜日か月曜日か仕様が求める方(うげ)。それをどうやって求めるか。
日曜日の場合
select s1.order_date ,s1.amount ,s1.order_date - day_of_the_week as hogeday from ( select date(order_date) as order_date ,amount ,cast(extract(dow from order_date) as int ) - 0 as day_of_the_week from orders where order_date between '2011/11/01' and '2011/12/01' order by day_of_the_week )s1 order by order_date
上記の場合、hogeday のところに、order_dateの週の日曜日の日付がでてくる。
月曜日にする場合は、真ん中ぐらいの day_of_the_week で「- 0」と書いてあるところを「- 1」にすればよし。
曜日の数字はゼロからはじまるので、自分の曜日の数字を自分の日付からマイナスすると、日曜日(あるいは月曜日)の数字がでる。注意点は、order_date を timestampにしていたので、そのままだと「日付 - 日数」がいきなり使えない。なので dateにしている。。。あ、cast文法とかあるんすね!!(万年初心者)
週の頭で集計する。
select sum(s1.amount) ,s1.order_date - day_of_the_week as hogeday from ( select date(order_date) as order_date ,amount ,cast(extract(dow from order_date) as int ) - 0 as day_of_the_week from orders where order_date between '2011/11/01' and '2011/12/01' order by day_of_the_week )s1 group by hogeday order by hogeday
できたー。
だがしかし。ちょっとした見た目の罠がある。
たとえば、日付の範囲指定を2011/11の一ヶ月にしていると、最初の週の週の頭は、10月の日付がでてきてしまう。抽出条件は11月なのに、10月も出てくる。。なので、検索結果を表示する場合は、最初の週の頭の月が変わっている場合は、範囲指定の開始の日付をぶっこんでしまうのが、お客さん? に何かいわれる可能性は低いかも。いやそもそもしかしだよ、週ごとに集計する場合、検索条件がどうなるかってーのはありますが。まま、そこらへんは決めごとなので好きにして。(←だめエンジニアはこういう言い方をする)
上記以外に書き方はあると思いまっするよ。
おまけ。
手動でテストデータをつくるのにせこせこINSERT文を作ったのだが
insert into orders(order_date, amount) select current_timestamp + '-42 days' ,11 ;
なりゆきでこんなSQLになった。'-42 days' のところと amountに入る数字「11」を色々かえた。最初のうち、42日前にするのに、さんざん「current_timestamp - '42 days'」と書いて怒られまくったよ。常に「プラス」で日付の数字の前に、マイナス書くらしいっすよ。(たくさん作るならプログラムからつくるべきだも)