JSONBとは——PostgreSQL最強のデータ型
PostgreSQLには JSON と JSONB という2種類のJSON型があります。
| 比較項目 | JSON | JSONB |
|---|---|---|
| 保存形式 | テキスト(入力そのまま) | バイナリ(パース済み) |
| 挿入速度 | 速い | やや遅い |
| 検索速度 | 遅い | 速い |
| インデックス | 不可 | GINインデックス対応 |
| キー順序 | 保持 | 保持しない |
| 重複キー | 保持 | 最後のみ残る |
実務ではJSONB一択です。検索・インデックスが使えるため、JSONデータに対してSQLで柔軟にクエリが書けます。
Laravelでのマイグレーション
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 10, 2);
$table->jsonb('attributes')->default('{}'); // JSONB型カラム
$table->jsonb('images')->default('[]');
$table->timestamps();
});
モデルの設定
$casts に 'array' を指定するだけで、PHPの配列として透過的に扱えます。
class Product extends Model
{
protected $casts = [
'attributes' => 'array',
'images' => 'array',
];
}
データの保存と取得
// 保存
Product::create([
'name' => 'ワイヤレスイヤホン',
'price' => 12800,
'attributes' => [
'color' => 'ブラック',
'weight' => '45g',
'battery' => '8時間',
'bluetooth' => '5.3',
],
]);
// 取得(PHPの配列として使える)
$product = Product::find(1);
echo $product->attributes['color']; // "ブラック"
echo $product->attributes['battery']; // "8時間"
// 部分更新(マージ)
$product->attributes = array_merge(
$product->attributes,
['bluetooth' => '5.4']
);
$product->save();
JSONBのクエリ——Laravelから検索する
特定キーの値で絞り込み
// 'attributes->color' が 'ブラック' の商品を検索
$products = Product::where('attributes->color', 'ブラック')->get();
// JSONBの値で数値比較(->演算子はテキストを返すのでキャスト必要)
$products = Product::whereRaw(
"(attributes->>'weight')::numeric < ?", [50]
)->get();
GINインデックスで高速化
// マイグレーションでGINインデックスを追加
DB::statement("CREATE INDEX products_attributes_gin ON products USING GIN (attributes)");
// @> 演算子(含む)でインデックスが使われる
$products = Product::whereRaw(
"attributes @> ?::jsonb",
[json_encode(['color' => 'ブラック'])]
)->get();
実践:商品スペック管理システム
ECサイトでは商品カテゴリによってスペック(属性)が異なります。家電・衣料・食品では全く異なる属性があり、JSONB型はこのような可変スキーマに最適です。
// 家電の場合
$electronics = Product::create([
'category' => '家電',
'attributes' => [
'voltage' => '100V',
'power' => '1200W',
'dimensions' => ['width' => 30, 'height' => 20, 'depth' => 25],
'warranty' => '1年',
],
]);
// 衣料の場合(同じテーブル・同じカラム)
$clothing = Product::create([
'category' => '衣料',
'attributes' => [
'size' => ['S', 'M', 'L', 'XL'],
'material' => '綿95% ポリエステル5%',
'washable' => true,
'color_codes' => ['#000000', '#FFFFFF', '#1a73e8'],
],
]);
注意点——アンチパターン
JSONB内を頻繁にソート・集計するのはNG
JSONB内の値でソートや集計が頻繁に必要なら、それはテーブルのカラムとして切り出すべきです。JSONBは「柔軟な属性」に向いており、「集計の軸になるデータ」には向きません。
// NG:価格をJSONB内に入れてソートしようとする
// OK:価格は専用カラムで管理
$table->decimal('price', 10, 2); // 正規カラムとして持つ
まとめ
PostgreSQLのJSONB型とLaravelの組み合わせは、リレーショナルDBの堅牢性とドキュメントDBの柔軟性を両立できる強力な手段です。弊社では商品属性・設定情報・外部APIのレスポンスキャッシュなど、様々な場面でJSONBを活用しています。
PostgreSQL・LaravelでのWebシステム開発についてはお気軽にご相談ください。