ぽんぽんぺいんでつらたんなので、おしごとおやすみしまーす

🗣: バイクに乗ったり、ものづくりしたり、ひたすら寝たり。

SQLアンチパターン論理設計編の追記

こんばんわ!Paypayを導入した翌日に20%還元イベントが終了した者です。
意外にコンビニで「Paypayで」とお願いすると困惑される事も多くて、まだまだ10日では日本国民に浸透していない雰囲気ありますね?
セブンイレブンでは怪訝な反応をされましたが、ファミマはPaypayアナウンスが入る程度に対応が早かったですね)

しかしアマゾンとメルカリさんが経団連の面子に入ったことから、IT分野が経済圏の掌握をしていく未来は避けられなさそうですね。扱うジャンルのものにも依ると思うのですが、再度金融商品周りの勉強をしても損はなさそうな気がしています。
UI周りの重要度の認識も上がるといいのですけど!
あとは著作物の権利関係かな。民間資格で言うとビジネス著作権検定などもありますので、資格コレクターな方は是非是非。


経営面も強くなりたいので、バランスシートを読む練習はしたいなーと思いつつ全然やってないですね…
会社を持つ気にはなれないのですが、身を置いている会社がどんな状況にあるかは把握しておきたい的なやつです。
今話題の基本用法技術者の出題範囲でもあります。

それよりもカジュアルに、エンジニアによるエンジニアの為の生存戦略ベンチャーの経営状況を把握するみたいな標題の勉強会がそのうち開催されることを夢見てます。笑



本題です。なんとSQLアンチパターンまとめの目次抜けがありました🤣
自らまとめると意気込んだにも関わらず、この体たらくです。本は1回読んだだけじゃダメですね。
自分はかなり物覚えも悪いので、だいたい目に付くところに提出書類をまとめたファイルをぶら下げてあります。
とりあえず目に入らない情報は綺麗に忘れていく

エンジニアの勉強で難しいところは、個人的に触って実践できる部分と、本番でなければその利点や使いづらさを身にしみて理解できない部分があるところだと思います。

本番の再現ができる技術レベルでもない限り、私たちにできることは、なんとなく勉強会などで聞きかじったことを、経験のある人に話題として振ってみるくらいのことしかできないので、
今後は自分のスキルを積み上げる為にも、初心に戻ってシステム開発の基礎+他言語+フレームワークを触る予定でいます。
近いうちに〇〇と〇〇ができる人!って感じに頼られたいですからね!笑


ようやく本題。今回ご紹介するDB設計アンチパターンはこの2つ!

マルチカラムアトリビュート(複数列属性)
メタデータトリブル(メタデータ大増殖)


マルチカラムアトリビュート(複数列属性)

この事例を読んだ時には、「あー。これこれ。よくやるやつ本当これ」ActiveRecordを触っていた時によく思っていたこととがっつり同期しました。

なんか増えそうな項目なんだけど、とりあえずカテゴリ分けしておきたい時
Bugsというテーブルがあったとします。
このテーブルは単一テーブルでバグの発生日時、発生順、バグの内容、バグの起因を表現しています。

bug_id バグの発生順
reported_at バグの発生日時
issue バグの内容
case1 バグの起因(ユーザー起因)
case2 バグの起因(開発者起因)
case3 バグの起因(運用起因)

こんな感じで設計してみました。ただ、このcase*カラムは未使用のものに関してNULLを返す仕様になっています。

この設計で困る時

なんか検索クエリ長い

起因が複数にまたがる時は以下のようなクエリを発行したくなります。(INを使えばもう少しマシになる)

select * from Bugs
where (case1 = 'user' or case2 = 'developer' or case3 = 'customer service')
and (case1 = 'user' or case2 = 'developer' or case3 = 'customer service');

値を更新したいけど更新処理が突合しないか

どの列が空いていないかを確認し、更新処理をかけたところで他のユーザーが同じデータの列に更新処理をかける可能性も否めないわけです。
しかし、この事例ではNULLIF関数を使用することでそれが避けられそうだというソリューションが乗っていました。

update Bugs
set case1 = NULLIF(case1, 'user'),
set case2 = NULLIF(case2, 'developer'),
set case3 = NULLIF(case3, 'customer service')
where bug_id = 1234;

ちょう便利!しかし、これでfalseを返す場合にデータの更新処理は行われません。
従ってIF文を書かなければいけないわけです(もうコレアプリ側でやるやつやん…

万が一属性が増えた場合に、クエリがさらに長くなる
スキーマを変更したいときにテーブル自体をロックしなければいけない可能性
新規テーブルを定義して既存テーブルをコピーしてマイグレートするサードパーティ製品もあるので気をつける(地獄

ソリューション

caseを切り出してテーブルにしてしまうのが良いようです。縦管理で心置きなくdelete文を発行できます😇

メタデータトリブル(メタデータ大増殖)

本棚を買ったけど、スペースがどうにも気になって読まない本まで入れてしまうやつ。

本書の事例では、ある顧客管理テーブルと関連性を持った年毎の顧客データ格納テーブルを設計した話が記載されています。
使用用途はアクティブな顧客を一目でわかるようにしたいとのこと。

しかし毎度クエリを発行するのがめんどくさい怠惰なCSチームはもれなくこのテーブルを放置プレイする訳です。おめーらが作れって言うてたやんけ!
開発者の中ではこのテーブルをどう活用するのか議論になりますが、下記のようにおえかきじゆうちょうもびっくりなフリーダムなテーブルへと変貌を遂げます。

年毎のテーブルを毎年createする必要がある
データの整合性をチェックする必要がある

私は正規表現などで弾くものかと思ってましたが、
CHECK制限を儲けるとこのあたりは捗るみたいです。

create table Bugs_2018 (
date_reported DATE CHECK (EXTRACT(YEAR FROM date_reported) = 2018)
);

他の年度とjoinしたテーブルから特定の値を抜きたい要件では、発行するクエリがめんどくさい感じになる…

ソリューション

パーティショニングする
やっぱり別枠で切り出す😇

パーティショニング is 何?

私もこの章を読んで初めて知りました。パーティショニングとは、

テーブルをデータベース内部で複数の領域(指定キー)に分割する機能

だそうです。

私のイメージではAテーブルにA'というテーブルが違うレイヤーにあるイメージとして捉えています。

本書で発行されるクエリは以下のようなものです。

水平パーティショニングされたテーブルの作成の例

4分割されたテーブルにdate_reportedがはいる。

create table Bugs(
bug_id SERIAL_PRIMALY_KEY
date_reported DATE
) PARTITION BY HASH ( YEAR(date_reported) )
PARTITIONS 4;

パーティションはpostgres, Oracle, mysqlではざっと対応していそう)

垂直パーティショニング

水平パーティショニングは行で(1レコードずつ)分割するが、
垂直パーティショニングは列で分割をする。格納データが膨大な場合に後者を使用する。


\-- 関連テーブルを外部キーで参照するのもパーティショニングのテクニック

create table ProductionInstallers (
product_id BIGINT UNSIGNED PRIMARY KEY,
installer_image BLOB,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

例えば、ブログ記事に見出し画像を設定したい場合にバイナリで持つ場合にした時に関連テーブルを持たせる雰囲気です。(BlogDetail?)

この辺あやふやなので後ほど加筆します!



ラクル社の提供する講座&pdf
第10回 パーティションについて
https://www.oracle.com/technetwork/jp/ondemand/database/db-technique/d-12-disk-1484778-ja.pdf