売上を顧客の締日で集計するSQLを簡単に書く方法
SQLそれ自体は一番最後にあります。
天才的なソリューションだった。驚いた。日付から締日を引き算すれば、何月締めであるかを判定できるとは。シカクいアタマをマルくする必要性を痛感した。
締日集計のめんどいところは、締日がN件あること。5日、10日、15日、20日、25日、月末の6パターンある場合、2017年12月の締日別の合計は以下の期間を元に算出する必要がある。
2017年12月の締日別集計期間
締日 | 集計期間 |
---|---|
5日 | 2017/11/06〜2017/12/05 |
10日 | 2017/11/11〜2017/12/10 |
15日 | 2017/11/16〜2017/12/15 |
20日 | 2017/11/21〜2017/12/20 |
25日 | 2017/11/26〜2017/12/25 |
月末 | 2017/12/01〜2017/12/31 |
当たり前だが、月末の末日は変化する。28、29、30、31の4通りある。締日別で売上を見たいというのは、上記の集計期間別に売上総額を見て、入金予定の算段を立てる目安として使われる事が多い。
この締日別の集計期間をどう作るべきか悩んでいた。最低最悪のソリューションだと締日別にSQLを投げてUNIONするのはありえへんし、CASE-WHENで締日を見てクロス集計するのも無駄に複雑でだるい。そこに彗星の如く現れたのが「日付から締日を引き算」するというSQLだ。実際にやってみた。
伝票日付から締日を引くSQL(サンプル)
select customers.duedate, earnings.billdate, (earnings.billdate - interval customers.duedate DAY) as monthly from earnings JOIN customers ON customers.id = earnings.customer_id where year(earnings.billdate) = 2017 group by earnings.billdate,customers.duedate
MySQLの場合、日付型のデータにおいてはINTERVALを与えると引き算(足し算)してくれる。実際に引いてみたら、たしかにこれで集計できることがわかった。あとは簡単。DATE_FORMATで年月を割り出し、そいつでGroupByするだけだ。
締日別売上集計SQL
select duedate, DATE_FORMAT((billdate - interval duedate DAY),'%Y年%m月') as monthly, sum(subtotal) from earnings JOIN customers ON customers.id = earnings.customer_id group by monthly,duedate order by monthly,duedate
手元のMacで8万行の売上データが507msで集計できた。
追記(2017.12.26 17:30)
duedate(締日)に31が登録されている場合(うちのシステムは、末締めの場合31という数値を入力して表現することになっている)月末が31日じゃないものについてはバグになってしまう。11/30から31を引いたら、10/30になってしまい11月伝票が10月伝票になってしまうことに気がついたので、このSQLはクソ。駄目です。
というか、末締めの場合は締日の引き算が不要。その日付の年月が締め対象になる。というわけで、そこをCASE-WHENで場合分けすればOKだった。以下のSQLなら大丈夫なはず。
select duedate, case when duedate = 31 then DATE_FORMAT(billdate,'%Y年%m月') else DATE_FORMAT((billdate - interval duedate DAY),'%Y年%m月') end as monthly, sum(subtotal) from earnings JOIN customers ON customers.id = earnings.customer_id group by monthly,duedate order by monthly,duedate
追記(2018.03.23 14:40)
バグが有ることが判明しました。
20日締で5/25付の顧客は6月付にならなければなりません。5/21〜6/20 が期間なので。でも、5/25 で 締日が20日の場合、単純に日付から締日を引き算してしまうと、5/5になり、5月付になってしまいます。5/25は6月付やん。アカンわ。
「日付の日にち」>「締日」だったら日付の年月を1ヶ月加算、「日付の日にち」<=「締日」はその日付の年月を請求月とする。これが正しいようです。末締の場合は何も加算する必要が無いので注意が必要です。
締日5日で検証
締日 | 日付 | 請求月 |
---|---|---|
5日(2017/10/06〜2017/11/5) | 2017/10/25 | 11月締 |
5日(2017/10/06〜2017/11/5) | 2017/10/4 | 10月締 |
締日10日で検証
締日 | 日付 | 請求月 |
---|---|---|
10日(2017/10/11〜2017/11/10) | 2017/10/9 | 10月締 |
10日(2017/10/11〜2017/11/10) | 2017/10/31 | 11月締 |
締日15日で検証
締日 | 日付 | 請求月 |
---|---|---|
15日(2017/10/16〜2017/11/15) | 2017/10/9 | 10月締 |
15日(2017/10/16〜2017/11/15) | 2017/10/31 | 11月締 |
締日20日で検証
締日 | 日付 | 請求月 |
---|---|---|
20日(2017/10/21〜2017/11/20) | 2017/10/9 | 10月締 |
20日(2017/10/21〜2017/11/20) | 2017/10/24 | 11月締 |
締日25日で検証
締日 | 日付 | 請求月 |
---|---|---|
25日(2017/10/26〜2017/11/25) | 2017/11/10 | 11月締 |
25日(2017/10/26〜2017/11/20) | 2017/11/28 | 12月締 |
良さそうですね。締日よりも先の日付(締日は含まない)だったら、1ヶ月先が請求月。正しい気がします。
というわけで、締日集計のSQLの改訂版です。
SELECT CASE WHEN duedate = 31 THEN DATE_FORMAT(billdate,'%Y年%m月') WHEN duedate < 31 AND DAY(billdate) <= duedate THEN DATE_FORMAT(billdate,'%Y年%m月') WHEN duedate < 31 AND DAY(billdate) > duedate THEN DATE_FORMAT(DATE_ADD(billdate, INTERVAL 1 MONTH),'%Y年%m月') END AS monthly, duedate, sum(subtotal) FROM earnings JOIN customers ON customers.id = earnings.customer_id WHERE duedate > 0 GROUP BY monthly,duedate ORDER BY monthly,duedate
念のため、締日での請求月の算出動作確認用SQLも貼り付けておきます。
SELECT billdate, -- 伝票日付 duedate, -- 顧客の締日(1〜31) CASE WHEN duedate = 31 THEN DATE_FORMAT(billdate,'%Y年%m月') -- 末締は伝票月=請求月 WHEN duedate < 31 AND DAY(billdate) <= duedate THEN DATE_FORMAT(billdate,'%Y年%m月') -- 末締以外で、伝票日 <= 締日なら、伝票月=請求月 WHEN duedate < 31 AND DAY(billdate) > duedate THEN DATE_FORMAT(DATE_ADD(billdate, INTERVAL 1 MONTH),'%Y年%m月') -- 末締以外で、伝票日 > 締日なら、伝票月=請求月の翌月 END AS monthly FROM earnings JOIN customers ON customers.id = earnings.customer_id WHERE duedate > 0 GROUP BY billdate,duedate
2017年度の振り返り
自分一人が食っていく分には困らないけど、事業を作っていくのは本当に大変だなと改めて実感。サービスを販売して軌道に乗せるためには、年商1000万は無いとだめ。年間100万で売るなら10本、年間1万円にするなら100本売らないと販管費や広告宣伝費等を含めるとペイしない。ここまで育て上げるのに使うお金は、1000万では効かない。自分の人件費等を含めたら。
業務委託で自分のスキルを売るのが、手っ取り早く売上になって定期的にお金が入ってくるので、すごく楽です。僕自身も(かなり自由にやらせて頂いてますが)お世話になっている。スキルがあれば買ってくれる人は、どこかしらにいる。スキルの切り売り感は個人的には全く無いし、何かしら新しい発見があるよね、問題意識さえ持っていれば。いつまでもこのままお世話になるのは嫌だけど、生命維持装置のようなもので、なかなか外すことが困難。
サービスを販売するための原資や時間を捻出する為に、自分のリソースを売りながらチャンスを掴もうとしている。ニーズがない商品を作ってもしょうがないので、ニーズを探すために色んな所へ顔を出した。
チャンスの女神の前髪が見えてきたのが、2017年度のハイライト。SIビジネスを変えうるお話を頂いたし、昔から諦めきれない販売管理でも多くの引き合いを頂いた。2018年はシーズを育ててソリューションへと結実させて新しいステージへと進む。
前途洋々、頑張れおれ。
Androidでバーコードリーダーを作る
Andoridでは、だいたい2年ぐらい前からGoogle公式でバーコードをカメラで解析するAPIが搭載されています。
Barcode Detection with the Mobile Vision API
このAPIがクセがあって「カメラに写っているバーコード全てを認識できる」というものになっています。それはそれですごいのだけれども、「ここが読取り範囲だよ」という範囲を指定して単一のバーコードしか読まないようにする事が出来ない。
そこを制御してくれるのが、下記のGitHubのライブラリでした。導入すごい簡単でした。
github.com
Android6.0以上から、実行時にカメラへのアクセス許可が必要になった。そのハンドリングをするコードを書かないと動かないので、ご注意を。
Prismに慣れてきた。これがMS標準だったらなぁ・・・
WPFアプリ構築の敷居もグッと下がったと思う。このライブラリがMicrosoft標準でないというのが、WPFの悲劇なのでは。
Prismを使ってWindowsのデスクトップアプリを作っているのですが、だいぶ慣れてきた。かずきさんのレポジトリを見て写経すればまずOK。MVVMのやり方と画面遷移のあり方が書いてあるので、その2つがあれば生きていける。Prismはよく出来ているライブラリですわ。
XAMLが好きになってきた
最初はなんだよこのハイパー拡張HTMLはと思いましたが、XAMLに慣れてしまうとWindowsフォームで画面を作るのは無茶苦茶だるくなりました。テキストで全部が表現されているのは正義だ。似たような画面作るのすごい楽。ViewModelのインスタンスはDIしてくれるのも楽なんだよな〜
画面遷移が便利すぎて感動
Region機能を使わないでPrismでアプリを使うことはありえない。今回、こいつがすごく便利でびっくりしている。
HTMLのコーディングと一緒で、MainWindowにヘッダーとフッターだけは共通化して定義。あとは「Contents」みたいな感じにして、そこだけPrismからユーザーコントロールを当て込む。これだけで生きていけるようだ。
かずきさんのレポジトリにあるように、PrismのUnityBootstraperの機能で画面とViewModelが予め全て初期化された状態でアプリが起動してくれるので、画面遷移した時は遷移前の状態が残る。残ったほうが便利なんだよね、間違いないんだよね。
ダイアログを起動してコールバックをするというのは、IntercationRequestなるものを使えばいける。YES/Noも取れるし、画面を起動する時にその都度パラメーターを与えてバインドさせることもできる。任意の型のパラメーターを受け渡すことができる。
ダイアログではなく、Contentsページの切り替えも簡単にできる。当該画面が画面遷移によって表示される時と、当該画面が遷移によって消える時の両方にコールバック関数を定義することができるし、任意の型のパラメーターの受け渡しもいける。
RegionManagerクラスのインスタンスは、任意のViewModelのプロパティにDIすることができる。親画面のメニューをクリックして画面遷移、データグリッドをEnterひっぱたいて画面遷移の2パターン等が多いと思いますが、この2つは別のViewModelですよね。でも、PrismがDIしてくれることで、同じインスタンスがSingletonで引き回せるため、どのVMからも画面操作が可能。これは便利。
5年ぐらい前のWindowsフォームで作ったシステムのように、PanelにAddControl/RemoveControlしていた原始的なやり方に比べると、近代的で大変すばらしい。
コードビハインドからのVM
DataContextプロパティをDIされたViewModelにキャストすれば、どうとでもなる。
ただ、ほとんど使わない。Commandプロパティにバインドできるし、KeyイベントもInputBindingsを使えばどうとでもバインドできる。コードビハインドを余儀なくされたのは、任意の画面のコンポーネントにフォーカスを移す時。それ以外は殆どCommandをバインドすればいけるんじゃないかな。ViewModelを継承すると、更に便利。イベントは一緒だけど処理が違う(コマンドの中身が違う)という差分だけ実装すれば、生きていける。
DataGridの編集可能カラムで発生するキーイベントについては、コードビハインドで書いた。ViewModelとは関係なく任意のロジックを持っているクラスに処理を投げたかったので。
引き続き頑張ります
まだ5画面ぐらいしか作ってない。本番では60画面ぐらいになる予定なので、重くないかどうかはとても気になっている。でも、Prism+WPFの組み合わせは最高に便利だし、生産性も高い。引き続き頑張るぞい。
デスクトップアプリは、一周回ってWPFでいいじゃんと思った話
aroundthedistance.hatenadiary.jp
これは、2016年になってから前職の内製したWindowsフォームの密結合過ぎるデスクトップアプリを、WPFで刷新している時に書いた記事です。画面がレスポンシブじゃないのを除けば別にWPFやる必要なくねって書いていました。できること同じじゃん、っていう。
が、この時から1年半。もう1回WPF(with Prism)に向き合ってみると、PrismのようなMVVMライブラリを活用できなかった自分がアホやっただけなのでは、という気持ちがドンドン大きくなってきました。
Windowsフォーム→WPFに焼き直すメリットがあまり無いのも事実でしょうが、新規アプリでWindowsフォームを採用するメリットも特に無いわけです。
レイアウトを作るのはWPFの圧勝
改めて思いました。XAMLで画面デザインできることに慣れると、Windowsフォームはだるくてしょうがない。
Windowsフォームは絶対配置が基本ですが、TableLayout的なコントロールを使えば相対配置が可能です。でも、カラムに置くことができるコントロールが1個しか無い。この時点でかなりきつい。Dock/Anchorを活用すれば画面サイズの変更に伴って要素のサイズも変動できるけど、ちまちますべての要素にそれを設定するのはだるすぎた。
XAMLやリソース定義したスタイル設定、強力なGridLayoutやPanelコントロールになれてしまうと、戻れないですね... UI回りのカスタマイズが本当にやりやすくなっている。
MVVM、やっぱりイイ...
1年半前はPrismよくわかんないで素のMVVMやってたけれど、挙動が安定しなかった。Prismを勉強(GitHubのサンプルレポジトリの写経)してみると、思いの外簡単だった。DIでViewModelをインジェクションしてくれるの、すげー楽。ライフサイクルの管理もしなくて良いので、大変助かる。
MVVMで業務系アプリ作っていて、問題となるのはこの辺だと思う。
- キーイベントとCommand対応
- 親画面⇔子画面連携
- 画面遷移
- ViewModelの共存
KeyBindingsって機構があればキーイベントに対してコマンドが実行できる事を知った。これが上手くできれば、コードビハインドを書く必要が全くといいほど無い。残りの3つは、Prismがサポートしてくれる。ViewModelにViewの状態を集約できれば、似たような画面が多い業務系のアプリでは再利用性が高くなるので、MVVMを活用しない手は全く無い。
フォーカス制御だけはどうしようもない
これはWPF/Windowsフォームどっちも一緒。フォーカスが正しく制御されるUIに慣れてしまった人は、もうマウスクリックには戻りたくはない。そら、そうやね。入力はマウスレスが理想。検索・集計・出力系は、右クリックメニューが活躍するので、マウスレスはそこまで必要とされていない感触。
フォーカス制御する場合は、画面のコンポーネントの名前解決ができないといけないので、Region/Moduleと言った疎結合を実現するPrismの恩恵に預かることは困難だけど、それ以外はすごく楽。画面遷移や遷移時の値の引き継ぎも簡単にできる。Prism、いいっすね~。はじめから使っていればと悔やまれる。
WPF with Prism6に挑戦
販売管理システムのデモを求められており、昔に作ってお蔵入りしたWPFベースのアプリを元にPrismを使って移植手術することにした。Windowsフォームで新しいアプリを作る気になれなかったし、WPFはMVVMと画面遷移がピタッとハマれば作りやすいと感じた。
オレオレ実装で死んだあの日
昔に作ったのは確か2年以上前。オレオレ実装で適当にやったことで、以下の問題が発生した。
- ビューがめっちゃ重い
- Core 2 Duoでカツカツ
- Celeron? 知らない子ですね
- MVVMの挙動が一致しない
- フレームワークを使わなかった
- Viewの状態がリフレッシュされず、変な挙動があった
キーイベントを包括的に拾えるっぽい
オレが不勉強だった。WPFにはビヘイビアという機能があり、UIEement(例えばTextBox)に共通のキーイベント等の処理をフックすることができる。これがあればUIのコンポーネント化が加速でき、同じTextBoxだけど何らかの分類をすることで、ビヘイビアの有無を切り替えることもできると思われる。それだけでいいんです。マジで。
オレオレMVVMはやらない
Prismがよくわからなくて、ViewModelの作り方やコードビハインドからの呼び出し方、画面遷移等の設計がGDGDだった。MVVMもオレオレでやってしまった。今回は画面構成・遷移・MVVMを全てPrismのアーキテクチャに乗っけて作ってみることにする。
Prismのサンプルプロジェクトやかずきさんの自習レポジトリを見てみると、おおよその流れはわかった。クラスライブラリとしてViewをインジェクションできるModuleという機能を使うとDDLで組み込めるらしいので、プレコンパイル済のJSPのような感じで画面のロードが速くなってくれたら嬉しい。動的にViewを差し替えるアーキテクチャが最適化されている香りがする。
ViewModelの所もかなり簡単で、規則に沿ってViewModelを作れば勝手にDataContextに該当のViewModelがインジェクションされる。MVVMやるのが簡単になってるね。
Reactiveにどこまできるのか
ReactiveProgrammingを加速するライブラリもあるそうだ。最大の課題は、DataGridViewにバインドしたObservableCollectionの中身のオブジェクトの変更を検知する仕組み。Prismではないと思うから、その辺りに実績のあるライブラリ無いかな。RxJava的な。
Funcition Key 対応
これは絶対言われる気がするので、ちょっと考える。
tax_queryの検索が上手くいかない
配列をネストする必要がありました。これやからPHPは(ry
<?php //アカンやつ 'tax_query' => array( 'taxonomy' => 'XXXX', 'field' => 'slug', 'terms' => 'YYYY', ) //いけるやつ 'tax_query' => array( array( 'taxonomy' => 'XXXX', 'field' => 'slug', 'terms' => 'YYYY', ) )