LoginSignup
2
3

More than 1 year has passed since last update.

SQLアンチパターン 一言で表現してみた ~輪読会まとめ~

Last updated at Posted at 2021-12-11

はじめに

以前に引き続き、社内で輪読会を実施しました。
※前回の記事はこちら

今回は、SQLアンチパターンを題材として取り上げました。
前回のGoF本もそうですが、各章が独立に書かれた本は、輪読会で取り上げやすいです。

以前と同様に、各SQLアンチパターンを一言で表現してみる、という取り組みをしてみました。
狙いとしては
・「一言で表現する」という要約の過程で、さらに理解を深めることができる
・一言であれば後から思い出しやすい
という感じです。

今回は、アンチパターンについて焦点を当て、その問題点や解決策については記載しておりません。
アンチパターンが採用されないことが最も重要であり、そのためにアンチパターンをいつでも思い出すことができる状態にする、という方針です。
解決策を知りたい方は、ぜひ書籍をご購入ください。

参考図書

SQLアンチパターン

超オススメです。ただし、中級者向けなので、基本的なSQL構文やRDBについて勉強してから取り組むのがよいと思います。

SQLアンチパターン 一言で表現してみた

データベース論理設計のアンチパターン

1.ジェイウォーク(信号無視) <カンマ区切りでデータを格納する>

一つの属性の複数の値を格納したいとき、
VARCHARで文字数長めのカラムを用意する。その中に複数のデータをカンマ区切りで格納する。

CREATE TABLE Products ( 
 product_id SERIAL PRIMARY KEY, 
 product_name VARCHAR(1000), 
 account_id VARCHAR(100), -- カンマ区切りのリスト
製品ID(product_id) 製品名(product_name) アカウントID(account_id)
0001 Phone TypeA AAA,BBB,CCC
0002 Phone TypeB BBB,DDD,EEE,FFF

2.ナイーブツリー(素朴な木) <親のみに依存する木構造>

木構造を表現したいとき、
同テーブル内の、自身の親のIDを指定するカラムを定義する。

CREATE TABLE Departments ( 
 department_id VARCHAR(4) PRIMARY KEY, 
 department_name VARCHAR(10), 
 upper_department_id VARCHAR(4), ---同テーブルのdepartment_idを参照する
 FORREIGN KEY (upper_department_id) REFERENCES Departments(department_id)
部門ID(department_id) 部門名(department_name) 上位部門ID(upper_department_id)
1111 営業1課 1110
1112 営業2課 1110
1110 営業部

3.IDリクワイアド(とりあえずID) <とりあえず疑似キーを付ける>

行ごとに、データを識別するIDをつけたいとき、
とりあえず疑似キーを用意する(レコードごとにユニークなIDを用意するため)。
※疑似キーとは、データに対して後付けされた、意味を持たないID。「id」という列名の、16~32bitの整数値であることが多い。

疑似ID(id) 部門名(department_name) 部員数(member_count)
1 営業1課 40
2 営業2課 45

4.キーレスエントリ(外部キー嫌い) <外部キーを設定しない>

外部キーを設定しない。

5.EAV(エンティティ・アトリビュート・バリュー) <サブタイプ属性を設定する>

"サブタイプ名"という名前のカラムを用意し、その中にサブタイプ名を保管する。サブタイプ名によって、同じ行の"データ"の意味を変える。
新しい属性を追加したい場合に、列を新規追加しなくてよくなるのが狙い。

社員ID(id) サブタイプ名(attribute_name) データ(attribute_value)
1234 name 田中一郎
1234 department 営業部
1234 year_of_employment 2010
1234 class 総合職

6.ポリモーフィック関連 <参照するテーブルを指定するカラムを作成する>

行によって、外部参照するテーブルを変えたいとき、
参照するテーブル名を指定するカラムを作成する。

例えば、以下のように、'どのシステムに対するログか'によって、log_idを参照するテーブルが異なるとき(今回は'Sales(営業)'or'Purchase(購買)')。

ログID(log_id) システムタイプ(system_type) ログ(log)
1 Sales 21/10/05 新規契約
2 Purchase 21/10/06/購入手続き
3 Sales 21/10/06 新規契約
4 Purchase 21/10/06/購入手続き

以下のように、ログ検索のときに、Logsテーブルに自身のテーブル名が格納されているかをチェックする。

SELECT * FROM Sales AS a 
  INNER JOIN Logs AS b 
  ON a.log_id = b.log_id AND b.system_type = 'Sales'
 WHERE b.log_id = '1';

7.マルチカラムアトリビュート(複数列属性) <同一属性のカラムを複数用意する>

一つのカラムに複数の値を格納したい(ジェイウォークと同じ)とき、
同じ属性のカラムを複数用意する。

例えば、記事に対して、それがどういう記事かを表すタグをつけたいとき。
付けることができるタグの数を最大3とし、タグを格納する属性を3つ定義する。タグが3つ無い場合は、NULLにする。

CREATE TABLE Issues ( 
 issue_id VARCHAR(4) PRIMARY KEY, 
 tag1 VARCHAR(20), 
 tag2 VARCHAR(20), 
 tag3 VARCHAR(20), 
記事ID(issue_id) タグ名1(tag1) タグ名2(tag2) タグ名3(tag3)
1111 SQL データベース SQL好きさんと繋がりたい
2222 JAVA デザインパターン NULL
3333 NULL NULL NULL

8.メタデータドリブル(メタデータ大増殖) <同一属性を持つテーブルを複数用意する>

テーブルが大きくなりすぎることを防ぐために、テーブルの分割を試みる。
例えば、入室履歴を記録するテーブルを、年度ごとに用意する。

CREATE TABLE Entry_2008 ( . . . ); 
CREATE TABLE Entry_2009 ( . . . ); 
CREATE TABLE Entry_2010 ( . . . );

データベース物理設計のアンチパターン

9.ラウンディングエラー(丸め誤差) <小数値をFLOAT型で利用する>

テーブルに小数値(FLOAT型)を格納して、計算に利用する。

※別記事にまとめました
【実践】テーブル設計でFLOAT型を使用することの危険性について/Qiita

10.サーティーワンフレーバー(31のフレーバー) <列定義上で、使用する値を限定する>

イレギュラーな値が入力されるのを防ぐために、列定義上で入力可能な値を制限する。
例えば、CHECK制約やENUMなどを使用する。

CREATE TABLE Recipes (
 -- 他の列 . . .
 meat_type VARCHAR(20) CHECK (status IN ('Beef', 'Pork', 'Chicken')) );
CREATE TABLE Recipes (
 -- 他の列 . . .
 meat_type ENUM('Beef', 'Pork', 'Chicken') );

※ENUMはMySQL固有の機能です。

11.ファントムファイル(幻のファイル) <画像ファイルのファイルパスをDBに記録する>

画像などのファイルをDBに直接保存せず、ファイルサーバーに保存する。
そのパスを、DBに保存する。

ファイルID(issue_id) ファイルパス(path)
1111 /home/user/hoge/hoge.png
2222 /home/user/moge/moge.png

12.インデックスショットガン(闇雲インデックス) <インデックスが効果的に使用できていない>

以下の三つ
1.インデックスをまったく定義しない
2.インデックスを多く定義しすぎる、もしくは役に立たないインデックスを定義してしまう
3.インデックスを活用しないクエリを実行してしまう

クエリのアンチパターン

13.フィア・オブ・ジ・アンノウン(恐怖のunknown) <NULL値を一般的な値として使用する>

NULLはSQLでは、不明(unknown)という特別な意味を持つ値であるが、その性質を十分に理解しないままに使用すること
・NULLを一般的な値と区別せずに使用する(例えば、四則演算など)
・NULLを嫌い、NULLを一切使用しないテーブル設計にする(例えば、未入力を表現する値として、NULLの代わりに'-1'を使用するなど)

14.アンビギュアスグループ(曖昧なグループ) <GROUPしていないカラムを選択する>

データをグループ化して表示したいとき、GROUP BYで指定していないカラムをSELECTで指定する。

この場合(下記例ではorder_id)の振る舞いは、RDBMSによって異なる。
・MySQL→グループの最初の列の値が返される
・SQLite→グループの最後の列の値が返される
・その他→エラーになる

SELECT product_id, MAX(arrival_date) AS latest, order_id FROM Products INNER JOIN Purchase USING (order_id) GROUP BY product_id;

15.ランダムセレクション<インデックスが活用できないランダムサンプリング>

データをランダムに取得したい場合には、SQLのRAND()を使用することになるが、使用方法を間違えるとインデックスが活用されなくなる。

具体的には、下記のように、ランダムにソートを行うを行うと、インデックスが適用されないソート処理が発生するので、パフォーマンスが悪化する。

SELECT recipe_id, recipe_name FROM Recipes ORDER BY RAND() LIMIT 1;

16.プアマンズ・サーチエンジン(賢者のサーチエンジン)<キーワード検索にパターンマッチ述語を使用する>

キーワード検索をする際に、正規表現やLike述語などを使用する。

17.スパゲッティクエリ<別目的のクエリをまとめて一つにする>

様々な値の検索を一つのクエリでまとめて実行しようとすること。

18.インプリシットカラム(暗黙の列)<全列指定(アスタリスク)を使用する>

全列指定(アスタリスク)を使用する。

SELECT * FROM Recipes;

アプリケーション開発のアンチパターン

19.リーダブルパスワード(読み取り可能パスワード)<パスワードを平文でテーブルに格納する>

パスワードをテーブルに平文で格納する。
平文のパスワードを通信したり、認証に使用したりする。

20.SQLインジェクション<未認証の値を組み立て、クエリを構築する>

認証されていない値を使ってクエリを組み立てる場合、悪意を持つ値がクエリ内部に潜り込むことで、意図しないテーブル操作がされてしまうリスクがある(SQLインジェクション)。

21.シュードキー・ニートフリーク(疑似キー潔癖症)<疑似キーの欠番を許さない>

データ削除などで疑似キーに欠番ができた際に、新データの登録やキーの振り直しによって、欠番を埋めようとする。

〇キー振り直し前

キー(id) 果物名(fruit)
1 Apple
2 Orange
4 Grape

〇キー振り直し後

キー(id) 果物名(fruit)
1 Apple
2 Orange
3 Grape

22.シー・ノー・エビル(臭いものに蓋)<クエリ実行時のエラー処理を書かない>

コードを短くするためなどの理由で、クエリ実行時のエラー処理を書かないこと。
・クエリ実行時のAPIの戻り値を取得しない
・実際に構築されたクエリを、後から把握できない
など。

23.ディプロマティック・イミュニティ(外交特権)<SQLを管理対象から外す>

DBに関して、文書化やバージョン管理、テスト対象から外すこと。

24.マジックビーンズ(魔法の豆)<モデルとDAOが一体化している>

MVCのM(モデル)が、アクティブレコード(ORMをサポートするパターン)そのものである。
C(コントローラ)上に、モデル(=アクティブレコード)を使用した複雑なロジックが書かれている。

25.砂の城<運用後の姿を想定していない>

サービス運用前の想定が甘い。
・データサイズ
・マシンの性能
・バックアップ計画
・インシデントへの対応
などなど

まとめ

本を読み進めていく中で、「自身が過去に実施したテーブル設計やクエリ設計が実はアンチパターンだった」みたいなことが何度かあり、冷や汗が止まりませんでした・・
これも勉強ということで、今後は気を付けることにしましょう。

色々な気付きを与えてくれる、とても良い輪読会でした。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3