はじめに
最近、オライリー・ジャパンの「SQLアンチパターン」という本を読みました。
本書から学んだ知識をベースに、「1つのカラムにカンマ区切りの値を保存する」というアンチパターンについて、私なりの実例を交えながら解説したいと思います。
なお、本記事の実装例やデメリットの説明は、書籍の内容をそのまま転記したものではなく、私が再解釈した内容となっています。
参考著書
オライリー・ジャパン「SQLアンチパターン」
https://www.oreilly.co.jp/books/9784873115894/
問題のある実装例
以下のような書籍管理テーブルがあるとします。
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title VARCHAR(255),
tags VARCHAR(255) -- カンマ区切りでタグを保存
);
INSERT INTO books (title, tags) VALUES
('サバイバルTypeScript', 'TypeScript, 入門');
デメリット
-
検索が難しい
- LIKE演算子を使う必要があり、パフォーマンスが悪い
-- 'TypeScript'タグを含む書籍を検索 SELECT * FROM books WHERE tags LIKE '%TypeScript%';
- インデックスが効果的に使えない
- このように検索文字列の前にワイルドカード(%)がある場合、インデックスが効果的に使えません。
- これは、インデックスは先頭から順に検索を行う仕組みのため、文字列の途中や末尾からの検索では活用できないためです。
- そのため、タグを検索する度に全文検索が発生し、データ量が増えるほど検索が遅くなります。
- LIKE演算子を使う必要があり、パフォーマンスが悪い
-
データの整合性が保ちづらい
- タグ名が不統一になりやすい(「TypeScript」と「typescript」など)
-
文字列の長さに制限がある
- VARCHAR(255)のような固定長のため、格納できるタグの数に制限がある
- タグが増えていくと、制限に達して新しいタグが追加できなくなる可能性がある
-
更新が大変
- 特定のタグだけを削除したい場合、文字列操作が必要になる
- タグの位置によって更新のロジックが変わる(先頭、中間、末尾)
- カンマの扱いも考慮が必要(余分なカンマが残ったり、カンマの数が不適切になったり)
解決策:交差テーブルの作成
以下のように、多対多の関係を表現する設計に変更します。
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title VARCHAR(255)
);
CREATE TABLE tags (
id INTEGER PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE book_tags (
book_id INTEGER,
tag_id INTEGER,
PRIMARY KEY (book_id, tag_id),
FOREIGN KEY (book_id) REFERENCES books(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
この設計により、検索が容易になり、データの整合性も保つことができようになります。
まとめ
これまで「1つのカラムにカンマ区切りの値を保存するのは良くない」という漠然とした認識は持っていましたが、今回SQLアンチパターンを読んで自分で噛み砕いていく中で、なぜ良くないのかを具体的に理解することができました。
インデックスが効かない理由や、文字列長の制限や更新の大変さによる実用上の問題など、技術的な背景を知ることで、より良いデータベース設計の重要性を実感することができました。