データベースで実験データを永久保存する
これまでExpressサーバーで試料データを配列で管理してきました。問題は — サーバーを再起動するとデータがすべて消えることです。配列はメモリ上にしか存在しないからです。
実験室で結果を付箋に書くと風で飛んでしまいますが、実験ノートに書けば永久に残ります。データベースはWeb開発の実験ノートです — サーバーが停止しても、コンピュータを再起動しても、データは安全に残ります。
ファイル vs データベース
先ほどNode.jsでCSVファイルの読み書きを学びました。ファイルでもデータを保存できるのに、なぜデータベースが必要なのでしょうか?
| ファイル(CSV、JSON) | データベース | |
|---|---|---|
| 検索 | ファイル全体を読んで自分でフィルタリング | 条件を指定するだけで即座に検索 |
| 同時アクセス | 複数のプログラムが同時に書くと衝突 | 安全に同時アクセスを処理 |
| データ量 | 数万件で遅くなる | 数百万件でも高速に処理 |
| 構造の強制 | どんな形式でも保存可能(ミスのリスク) | カラム型を強制(INT、VARCHARなど) |
実験試料が10個ならExcelで十分です。しかし10万件の試料データから「ODが1.0以上で2024年3月に登録された血液試料だけ」を探すなら — ファイルでは苦痛ですが、データベースなら1行で済みます。
SQL:データベースと対話する言語
データベースに「このデータをちょうだい」「これを保存して」「これを削除して」と指示する言語がSQL(Structured Query Language)です。
SQLはプログラミング言語の中で最も簡単な部類に入ります。英語の文のように読めます:
SELECT name, od FROM samples WHERE status = 'fail';この1行は「samplesテーブルからstatusがfailの行のnameとodを取得して」という意味です。PubMedでキーワードを組み合わせて論文を検索するように、SQLは条件の組み合わせでデータを検索します。
テーブル:データの構造
データベースではデータは**テーブル(table)**の中に保存されます。Excelのシートとほぼ同じ構造です:
samples テーブル
┌────┬──────────────┬──────┬────────┬────────────┐
│ id │ name │ od │ status │ created_at │
├────┼──────────────┼──────┼────────┼────────────┤
│ 1 │ Blood-A │ 1.85 │ pass │ 2026-03-01 │
│ 2 │ Tissue-B │ 0.42 │ fail │ 2026-03-02 │
│ 3 │ Serum-C │ 2.10 │ pass │ 2026-03-03 │
│ 4 │ Plasma-D │ 0.15 │ fail │ 2026-03-03 │
└────┴──────────────┴──────┴────────┴────────────┘用語の整理:
- 行(Row) = 1つのレコード。試料1件分の全情報
- 列(Column) = 1つの項目。名前、OD値、ステータスといったデータの種類
- スキーマ(Schema) = テーブルの設計図。どのカラムがあり、各カラムのデータ型は何か
CRUD:データの4つの基本操作
すべての情報システムの核心はCRUD — 作成、読み取り、更新、削除です。実験室のLIMSも結局この4つの操作の組み合わせです。
CREATE — テーブルを作る
CREATE TABLE samples (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
od DECIMAL(5, 2),
status VARCHAR(10) DEFAULT 'pending',
created_at DATE
);INT— 整数(id、試料番号)VARCHAR(100)— 最大100文字の文字列(試料名)DECIMAL(5, 2)— 小数点を含む数値(OD値:小数点以下2桁)AUTO_INCREMENT— 新しい行が追加される時に自動で番号が増えるPRIMARY KEY— このカラムが各行の一意な識別子NOT NULL— 空の値を許可しない
プロトコルに「反応温度:___°C、時間:___分」と欄を作っておくのと同じです。構造を先に定義すれば、後で不正なデータが入るのを防げます。
INSERT — データを入れる
INSERT INTO samples (name, od, status, created_at) VALUES
('Blood-A', 1.85, 'pass', '2026-03-01'),
('Tissue-B', 0.42, 'fail', '2026-03-02'),
('Serum-C', 2.10, 'pass', '2026-03-03');idはAUTO_INCREMENTなので、手動で入れなくても自動的に1、2、3が割り当てられます。
SELECT — データを取り出す(最もよく使うコマンド)
-- 全件取得
SELECT * FROM samples;
-- 特定のカラムのみ
SELECT name, od FROM samples;
-- 条件検索
SELECT * FROM samples WHERE status = 'fail';
-- OD 1.0以上のみ、OD降順でソート
SELECT name, od FROM samples WHERE od >= 1.0 ORDER BY od DESC;
-- 件数を数える
SELECT COUNT(*) FROM samples WHERE status = 'pass';SQLの力はこの条件の組み合わせにあります。ファイルを開いてforループで1つずつ比較する必要はなく、条件を指定するだけでデータベースが最適な方法で見つけてくれます。
UPDATE — データを修正する
-- idが2の試料のODを再測定値で更新
UPDATE samples SET od = 0.98, status = 'pass' WHERE id = 2;WHERE条件を忘れるとすべての行が変更されます。実験ノートで1つの試料の結果だけ修正しようとして全体を上書きしてしまう事故と同じです。UPDATEとDELETEでは必ずWHEREを確認してください。
DELETE — データを削除する
-- 特定の試料を削除
DELETE FROM samples WHERE id = 4;
-- すべてのfail試料を削除(注意!)
DELETE FROM samples WHERE status = 'fail';リレーショナルデータベース:テーブル間の接続
「リレーショナル」という名前はテーブル同士が関係を結べるという意味です。試料テーブルと研究者テーブルを接続すると:
researchers テーブル samples テーブル
┌────┬──────────┐ ┌────┬──────────┬───────────────┐
│ id │ name │ │ id │ name │ researcher_id │
├────┼──────────┤ ├────┼──────────┼───────────────┤
│ 1 │ 田中研究 │◄─────────│ 1 │ Blood-A │ 1 │
│ 2 │ 鈴木分析 │◄─────────│ 2 │ Tissue-B │ 2 │
└────┴──────────┘ │ 3 │ Serum-C │ 1 │
└────┴──────────┴───────────────┘researcher_idが2つのテーブルを接続する鍵です。この構造があれば:
SELECT samples.name, researchers.name
FROM samples
JOIN researchers ON samples.researcher_id = researchers.id
WHERE researchers.name = '田中研究';「田中研究さんが登録した試料だけ見せて」— ExcelでVLOOKUPを使うのと似ていますが、数百万件でも一瞬で動作します。
どのデータベースを使うか
| 製品 | 特徴 | 適した場面 |
|---|---|---|
| MySQL | 最も広く使われるオープンソースDB | Webサービス(WordPress、大半のWebサイト) |
| PostgreSQL | 高度な機能、複雑なクエリに強い | 分析中心のサービス、Supabase |
| SQLite | インストール不要、1ファイルがDB全体 | ローカルアプリ、プロトタイプ、個人プロジェクト |
BioPlaygroundのようなプロジェクトはSupabase(PostgreSQLベース)を使っています。SQL構文はMySQLでもPostgreSQLでも90%以上同一なので、1つ学べば残りは簡単に適応できます。
やってみよう(Faded Example)
空欄を埋めてQC不合格の試料を検索するSQLクエリを完成させてください。
name, odFROMstatus = 'fail'ORDER BY od ;
よくあるエラーと解決法
Q: Table 'database.samples' doesn't existエラーが出ます
CREATE TABLEをまだ実行していないか、別のデータベースに接続しています。SHOW TABLES;で現在のデータベースのテーブル一覧を確認してください。
Q: UPDATEで1行だけ修正しようとしたのに全部変わりました
WHERE条件を忘れました。UPDATE samples SET status = 'pass'はすべての行のstatusをpassに変えます。必ずWHERE id = 2のような条件を追加してください。重要な操作の前には、同じWHERE条件でSELECTを先に実行して、どの行が影響を受けるか確認する習慣をつけましょう。
Q: VARCHARとTEXTの違いは何ですか?
VARCHAR(100)は最大100文字まで保存し長さを指定します。TEXTは長さの制限がほぼありません。試料名やステータスのような短いデータはVARCHAR、実験メモや長い説明はTEXTが適しています。
Q: SQLコマンドは大文字でないといけませんか?
いいえ。SELECTとselectは同じです。大文字は慣例に過ぎません。SQLキーワードを大文字、テーブル/カラム名を小文字で書くとコードが読みやすくなります。
Q: ターミナルではなくGUIでMySQLを見られるツールはありますか?
ターミナルでmysql -u root -pで接続するのが基本ですが、GUIクライアントを使えばテーブル構造とデータを視覚的に確認できます。
| ツール | 特徴 | 価格 |
|---|---|---|
| Sequel Ace | 軽量で高速。基本的なクエリ/テーブル管理に最適(Mac専用) | 無料 |
| TablePlus | 洗練されたUI。MySQL、PostgreSQL、SQLiteなど複数DB対応 | 一部無料 |
| MySQL Workbench | MySQL公式ツール。機能豊富だが重い | 無料 |
最初はSequel Ace(Mac)またはMySQL Workbench(全プラットフォーム)で始めることをお勧めします。SQLコマンドを直接打つことに慣れてからGUIを補助的に使うと、データを一覧で確認しながら作業できます。