最近仕事で以下ツールを使ってみている。
- Toggl track: タイムトラッキングツール
- todoist: Todoツール
- Anki: 記憶補助ツール
まだ本格的に使いだして間があまりないが、今の所凄く良いように感じているのでそれについて書く。
- toggl trackで自分が何に時間を使っていたかを記録する
- todoistでやることを全部管理する
- Anki で業務知識を覚える
- まとめ
データの削除機能において、何らかの理由でデータは残しておきたい場合には「削除フラグ」が使われがちだが、これは絶対にやめたい。
この場合は「削除テーブル」を用意してそちらにデータを移し、元テーブルからはレコード削除を行うようにするべきだと思っている。
それについて書く。
例えばある商品が販売されていたが、今は販売されていない商品が以下のようなテーブルとレコードで表現されているとする。
id | 商品名 | 価格 | 削除フラグ |
---|---|---|---|
1 | ガム | 100 | 1 |
2 | キャンディ | 120 | 0 |
id: 1のガムは今はもう販売されていないので削除フラグが1(true)になっている。
しかし、商品購入履歴などで必要なのでデータはそのまま残っている。
「取り扱い商品一覧データ」を取得する際[WHERE 削除フラグ != 1]の記載を忘れるともう事故になる。
これくらいだったら「そんな間違いをする方が悪い」のかもしれないが「常に意識しないといけない」というのが地味に難易度が高い。
どんな状況でも、なにか商品テーブルに関する事を行う時には必ず削除フラグの事を意識しないといけない。
一度でも忘れると、表示されてはいけないデータが表示されたりして事故になる。
なかなか厳しいのではないかと思う。
こんなのがそこかしらのテーブル内に○○フラグ、××フラグといくつもあったりするともうお手上げ。
テーブル定義はこんな感じ。頭文字をあわせることで関連したテーブルがテーブルリストで近なるようにしている。
商品全部テーブル(販売履歴など、商品とのリレーションはこのテーブルと行う)
id |
---|
1 |
2 |
商品販売中テーブル
id | 商品名 | 価格 |
---|---|---|
2 | キャンディ | 120 |
商品販売終了テーブル
id | 商品名 | 価格 |
---|---|---|
1 | ガム | 120 |
基本的に商品販売中テーブルを扱って作業するようになるので、特に気をつけないといけないことはない。
削除した商品のデータが必要な時は明示的に商品販売終了テーブルをJOINせねばならない。
直感的だと思う。
そもそもリレーショナルモデルにて集合は「状態」を扱わない。
フラグを持たせて状態を管理しだすとリレーショナルモデルから外れるためDBがカバーしきれなくなりアプリケーション側で面倒を見てやらねばならなくなる、というのが今回の問題の原因である。
だから、削除フラグを使わず削除テーブルを使うべきである。
参考図書)
理論から学ぶデータベース実践入門 ~リレーショナルモデルによる効率的なSQL (WEB+DB PRESS plus)
Gorpを使っていて以下のようなqueryを実行したい時がある。
select * from table where id in (1, 2, 4, 8);
"1,2,4,8"は動的に変えたいのでプレースホルダーにしたい。
select * from table where id in ($1, $2, $3, $4);
今回指定したidは4つだったけど、いつもそうとは限らないので渡す数も動的にしたい。
select * from table where id in ($1);
このqueryで以下のように実行してみるとエラーになる。
query := "select * from table where id in ($1);" dbmap := &gorp.DbMap{ Db: db, // コネクション Dialect: gorp.PostgresDialect{}, } _, err := dbmap.Select(mapper, query, []int{1,2,4,8}) // エラーが出る // sql: converting argument $1 type: unsupported type []int, a slice of int
以下のように書くと意図通りの挙動になる。
query := "select * from table where id in (:ids);" dbmap := &gorp.DbMap{ Db: db, Dialect: gorp.PostgresDialect{}, ExpandSliceArgs: true, //スライス引数展開オプションを有効化する } _, err := dbmap.Select(mapper, query, map[string]interface{}{"ids": []int{1,2,4,8}})
内部的には、query文字列を
select * from table where id in (:ids0, :ids1, :ids2, :ids3)
と展開して処理してくれている。
gorpのコードを読んでいてこの機能を知った。
この機能を知る前は、where in () の中を予めwhere in ($1, $2, $3, $4) となるように文字列をいじり、argsも[]interface{}型に変換してからdbMap.Select()に渡していたがめんどくさかった。。
GoでDBアクセスする為のサンプルコードを探すとどこもこんな感じ
func main() { # dbコネクション(pool)を取得 pool, err = sql.Open("driver-name", *dsn) if err != nil { // This will not be a connection error, but a DSN parse error or // another initialization error. log.Fatal("unable to use data source name", err) } defer pool.Close() # poolを使ってDBアクセスするコード ...... }
Openしたコネクションはアプリ終了時にdefer によってClose()処理されて終わる。理解した。
すべてmain()に処理があったり、数個の関数で構成されているアプリケーションだったら上記で良いけど、 クリーンアーキテクチャ的なレイヤー構造を持ったアプリケーションだったらどうすれば良いのか悩んだ。
続きを読む「このアプリケーションのこの機能は【年月日】までしか意識しない。【時分秒】はデータとして不要なのでテーブルにはdate型で【年月日】だけ持たせれば良い。」
と言われた事があったが例えアプリケーションにとって不要でもDBテーブルにはdatetimeとかtimestampで【時分秒】まで保持しておくべきだと思ったので書く。
アプリケーションを意識しまくったテーブル設計はアプリケーション要件に依存してしまう。
だからアプリケーション要件が変わるとテーブル設計を変更しないといけなくなる。
この時、既存データとの整合性をとる必要性まで出てきて大変になる。