Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
SQLアンチパターンから学ぶ『中間テーブル』を使用するメリット
Search
Kami Makoto
July 26, 2020
Technology
2
4.1k
SQLアンチパターンから学ぶ『中間テーブル』を使用するメリット
社内の勉強会の発表資料
Kami Makoto
July 26, 2020
Tweet
Share
More Decks by Kami Makoto
See All by Kami Makoto
DataHubを活用した集計基盤の構築過程と工夫したこと
kaminotsukai
0
750
AtCoderを1ヶ月全力でやって『体感』したこと
kaminotsukai
0
240
Other Decks in Technology
See All in Technology
EventHub Startup CTO of the year 2024 ピッチ資料
eventhub
0
120
アジャイルチームがらしさを発揮するための目標づくり / Making the goal and enabling the team
kakehashi
3
130
心が動くエンジニアリング ── 私が夢中になる理由
16bitidol
0
100
飲食店データの分析事例とそれを支えるデータ基盤
kimujun
0
150
オープンソースAIとは何か? --「オープンソースAIの定義 v1.0」詳細解説
shujisado
9
1.1k
BLADE: An Attempt to Automate Penetration Testing Using Autonomous AI Agents
bbrbbq
0
320
100 名超が参加した日経グループ横断の競技型 AWS 学習イベント「Nikkei Group AWS GameDay」の紹介/mediajaws202411
nikkei_engineer_recruiting
1
170
第1回 国土交通省 データコンペ参加者向け勉強会③- Snowflake x estie編 -
estie
0
130
OCI Security サービス 概要
oracle4engineer
PRO
0
6.5k
SSMRunbook作成の勘所_20241120
koichiotomo
3
160
ExaDB-D dbaascli で出来ること
oracle4engineer
PRO
0
3.9k
Flutterによる 効率的なAndroid・iOS・Webアプリケーション開発の事例
recruitengineers
PRO
0
120
Featured
See All Featured
Making the Leap to Tech Lead
cromwellryan
133
8.9k
Into the Great Unknown - MozCon
thekraken
32
1.5k
The World Runs on Bad Software
bkeepers
PRO
65
11k
Stop Working from a Prison Cell
hatefulcrawdad
267
20k
Art, The Web, and Tiny UX
lynnandtonic
297
20k
Keith and Marios Guide to Fast Websites
keithpitt
409
22k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Testing 201, or: Great Expectations
jmmastey
38
7.1k
The Art of Programming - Codeland 2020
erikaheidi
52
13k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
The Cult of Friendly URLs
andyhume
78
6k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
93
16k
Transcript
SQLアンチパターンから学ぶ! 「多対多には中間テーブルつけておけ」 の理由を理解する
ジェイウォーク - 信号無視
ジェイウォーク - 信号無視 id 1 1, 2 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1
2 user_id account ⼝座テーブル こんなテーブル⾒たことありませんか? ⼀つのカラムに『カンマ区切り』で複数の値が⼊っている
ジェイウォーク - 信号無視 id 1 1, 2 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1
2 user_id account ⼝座テーブル こんなテーブル⾒たことありませんか? これ、アンチパターンです!
ジェイウォーク - 信号無視 この現象はどうやって起きるのか? ⼝座 ユーザー ⼀⼈で複数の⼝座を持てる。 ⼝座は複数⼈で扱うことはできない ⼀⼈で複数の⼝座を持てる。 1つの⼝座(会社の⼝座)を複数⼈で扱える
⽇が過ぎて仕様変更が⼊る・・・ +
ジェイウォーク - 信号無視 この現象はどうやって起きるのか? id 1 ⼭本ジェイ ⽥中ウォーク 2 name
ユーザーテーブル (users) id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: int account: varchar(100) ⼝座テーブル (accounts) 3 2 zzzzzzz-zzzzzzz ← この⼝座は複数⼈で管理したい 少しでも変更を少なくしたいなあ エンジニアの気持ち
ジェイウォーク - 信号無視 この現象はどうやって起きるのか? id 1 ⼭本ジェイ ⽥中ウォーク 2 name
ユーザーテーブル (users) id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: varchar(30) account: varchar(100) ⼝座テーブル (accounts) 3 1, 2 zzzzzzz-zzzzzzz 1 user_idのカラムの型をintからvarcharに変更 2 user_idカラムにカンマ区切りでuser_idを挿⼊ 変更点
ジェイウォーク - 信号無視 これの何が問題なの? 1 ユーザーIDカラムの型がvarcharになったことで数値以外も⼊ってしまう 2 クエリの作成が難しくなる(例)ユーザーの共有解除 3 登録できるユーザーがuser_idの桁数によって変化してしまう
ジェイウォーク - 信号無視 1 ユーザーIDカラムの型がvarcharになったことで数値以外も⼊ってしまう id: int 1 1 xxxxxxx-xxxxxx
xxxxxxx-yyyyyy 1 2 user_id: int account: varchar(100) ⼝座テーブル (accounts) 3 2 zzzzzzz-zzzzzzz id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: int account: varchar(100) ⼝座テーブル (accounts) 3 Sample zzzzzzz-zzzzzzz id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: varchar(30) account: varchar(100) ⼝座テーブル (accounts) 3 2, Sample zzzzzzz-zzzzzzz 数値じゃない! ⽂字列が⼊るので問題なし! 変更前 変更後
ジェイウォーク - 信号無視 1 ユーザーIDカラムの型がvarcharになったことで数値以外も⼊るようになった id: int 1 1 xxxxxxx-xxxxxx
xxxxxxx-yyyyyy 1 2 user_id: int account: varchar(100) ⼝座テーブル (accounts) 3 2 zzzzzzz-zzzzzzz id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: int account: varchar(100) ⼝座テーブル (accounts) 3 Sample zzzzzzz-zzzzzzz id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: varchar(30) account: varchar(100) ⼝座テーブル (accounts) 3 2, Sample zzzzzzz-zzzzzzz 数値じゃない! ⽂字列が⼊るので問題なし! 変更前 変更後 なんでも⼊⼒できるので データが意味不明になる
ジェイウォーク - 信号無視 2 クエリの作成が難しくなる(例)ユーザーの共有解除 <?php $stmt = $pdo->query( “SELECT
user_id FROM accounts WHERE id = 3” ); $row = $stme->fetch(); $contact_list = $row[ʻuser_idʼ]; $value_to_remove = 2; // 削除したいユーザーのID $contact_list = explode(ʻ,ʼ, $contact_list); $key_to_remove = array_search($value_to_remove, $contact_list); unset($contact_list[$key_to_remove]); $contact_list = join(ʻ,ʼ, $contact_list); $stmt = $pdo->prepare( “UPDATE accounts SET user_id = ? WHERE id = 1” ); $stmt->excute(array($contact_list)); id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: varchar(30) account: varchar(100) ⼝座テーブル (accounts) 3 1, 2 zzzzzzz-zzzzzzz Q. この⼝座をユーザー2が扱えないように設定しなおしたい A
ジェイウォーク - 信号無視 2 クエリの作成が難しくなる(例)ユーザーの共有解除 <?php $stmt = $pdo->query( “SELECT
user_id FROM accounts WHERE id = 3” ); $row = $stme->fetch(); $contact_list = $row[ʻuser_idʼ]; $value_to_remove = 2; // 削除したいユーザーのID $contact_list = explode(ʻ,ʼ, $contact_list); $key_to_remove = array_search($value_to_remove, $contact_list); unset($contact_list[$key_to_remove]); $contact_list = join(ʻ,ʼ, $contact_list); $stmt = $pdo->prepare( “UPDATE accounts SET user_id = ? WHERE id = 1” ); $stmt->excute(array($contact_list)); $contact_list = ʻ1, 2ʼ; $contact_list = [1, 2]; $contact_list = 1; id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: varchar(30) account: varchar(100) ⼝座テーブル (accounts) 3 1, 2 zzzzzzz-zzzzzzz Q. この⼝座をユーザー2が扱えないように設定しなおしたい A 1. user_idのカラムのデータ を⽂字列で取得 2. explodeでカンマで分割した ⽂字列の配列を取得 3. 2で取得した配列から2を 削除したものを⽂字列で取得
ジェイウォーク - 信号無視 2 クエリの作成が難しくなる(例)ユーザーの共有解除 <?php $stmt = $pdo->query( “SELECT
user_id FROM accounts WHERE id = 3” ); $row = $stme->fetch(); $contact_list = $row[ʻuser_idʼ]; $value_to_remove = 2; // 削除したいユーザーのID $contact_list = explode(ʻ,ʼ, $contact_list); $key_to_remove = array_search($value_to_remove, $contact_list); unset($contact_list[$key_to_remove]); $contact_list = join(ʻ,ʼ, $contact_list); $stmt = $pdo->prepare( “UPDATE accounts SET user_id = ? WHERE id = 1” ); $stmt->excute(array($contact_list)); id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 1 2 user_id: varchar(30) account: varchar(100) ⼝座テーブル (accounts) 3 1, 2 zzzzzzz-zzzzzzz Q. この⼝座をユーザー2が扱えないように設定しなおしたい A SQL + PHP両⽅ゴリゴリ書かないといけない
ジェイウォーク - 信号無視 3 登録できるユーザーがuser_idの桁数によって変化してしまう id: int 1 ??? xxxxxxx-xxxxxx
xxxxxxx-yyyyyy 1 2 user_id: varchar(30) account: varchar(100) ⼝座テーブル (accounts) 3 2 zzzzzzz-zzzzzzz ʼ10,11,12,13,14,20,21,30,35,38ʼ 『10⼈』のユーザーが登録できる ʼ1020,1100,1210,1321,1433,2019,2100,3099…ʼ 『6⼈』のユーザーしか登録できない
ジェイウォーク - 信号無視 じゃあ、どうすればいいの? id: int 1 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy
1 2 user_id: int account: varchar(100) ⼝座テーブル (accounts) 3 2 zzzzzzz-zzzzzzz id: int 1 ⼭本ジェイ ⽥中ウォーク 2 name: varchar(30) ユーザーテーブル (users) 多 多
ジェイウォーク - 信号無視 じゃあ、どうすればいいの? id: int 1 xxxxxxx-xxxxxx xxxxxxx-yyyyyy 2
account: varchar(100) ⼝座テーブル (accounts) 3 zzzzzzz-zzzzzzz id: int 1 ⼭本ジェイ ⽥中ウォーク 2 name: varchar(30) ユーザーテーブル (users) user_id 1 3 1 2 account_id ⼝座とユーザーの中間テーブル (user_account_management) 2 3 1 1 多 多 中間テーブルを作る ※ 同じペアが複数存在する状況はおかしいので、 user_idとaccount_idのペアにUNIQUE制限をかけること!
ジェイウォーク - 信号無視 1 ユーザーIDカラムの型がvarcharになったことで数値以外も⼊ってしまう user_id: int 1 3 1
2 account_id: int ⼝座とユーザーの中間テーブル 2 3 数値しか⼊らなくなったので、 想定外の⽂字が⼊⼒されていないかを ⼼配する必要がなくなった!
ジェイウォーク - 信号無視 2 クエリの作成が難しくなる(例)ユーザーの共有解除 <?php $stmt = $pdo->query( “DELETE
FROM user_account_management WHERE account_id = 3 AND user_id = 2” ); $stmt->excute(array($contact_list)); user_id 1 3 1 2 account_id ⼝座とユーザーの中間テーブル (user_account_management) 2 3 このレコードを削除すると関連がなくなるので ユーザーを解除した扱いになる!
ジェイウォーク - 信号無視 2 クエリの作成が難しくなる(例)ユーザーの共有解除 <?php $stmt = $pdo->query( “DELETE
FROM user_account_management WHERE account_id = 3 AND user_id = 2” ); $stmt->excute(array($contact_list)); user_id 1 3 1 2 account_id ⼝座とユーザーの中間テーブル (user_account_management) 2 3 このレコードを削除すると関連がなくなるので ユーザーを解除した扱いになる! SQLオンリーで楽になった!
ジェイウォーク - 信号無視 3 登録できるユーザーがuser_idの桁数によって変化してしまう これに関しては、制限をかけないければ無限に登録ができるようになった! 逆に制限をかけたい場合は以下の⼿順で簡単に⾏える! (1)中間テーブルから特定の⼝座と紐づいているユーザー数を取得(COUNTなどで取れるよ!) (2)取得したユーザー数が制限数を上回っていないか確認 (3)追加で登録しても⼤丈夫なら中間テーブルに追加