DevToolBox無料
ブログ

PostgreSQL JSONBガイド:クエリ、インデックス、全文検索

13分by DevToolBox

PostgreSQL JSONB (Binary JSON) is one of the most powerful features in modern PostgreSQL. Unlike the JSON type which stores text verbatim, JSONB stores parsed binary data — enabling fast querying, indexing, and efficient storage. This guide covers everything from basic operations to advanced indexing strategies and performance optimization.

JSON vs JSONB: Which to Use?

PostgreSQL has two JSON types: json and jsonb. For almost all use cases, use jsonb.

Featurejsonjsonb (recommended)
Storage formatText (verbatim)Binary (parsed)
Write speedFasterSlightly slower
Read speedSlower (re-parse)Faster
GIN indexingNoYes
Duplicate keysPreservedLast value wins
Key orderingPreservedSorted
Operators-> and ->>All operators

Creating Tables and Inserting JSONB Data

JSONB columns store any valid JSON value. Keys are sorted and duplicates are removed on storage.

-- Create a table with JSONB column
CREATE TABLE products (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    metadata    JSONB,
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- Insert JSONB data
INSERT INTO products (name, metadata) VALUES
    ('Widget A', '{"price": 9.99, "category": "widgets", "tags": ["sale", "new"], "specs": {"weight": 0.5, "color": "red"}}'),
    ('Gadget B', '{"price": 24.99, "category": "gadgets", "tags": ["popular"], "specs": {"weight": 1.2, "color": "blue"}, "stock": 150}'),
    ('Doohickey C', '{"price": 4.99, "category": "widgets", "tags": ["sale"], "specs": {"weight": 0.1, "color": "green"}, "rating": 4.5}');

Querying JSONB: Operators Reference

PostgreSQL provides rich operators for accessing and filtering JSONB data.

-- Access a top-level key (returns JSONB)
SELECT metadata -> 'price' FROM products;
-- Returns: 9.99, 24.99, 4.99 (as JSONB)

-- Access a key as text (use ->>)
SELECT metadata ->> 'category' FROM products;
-- Returns: 'widgets', 'gadgets', 'widgets' (as text)

-- Nested access
SELECT metadata -> 'specs' -> 'color' FROM products;
SELECT metadata #> '{specs,color}' FROM products;        -- path operator
SELECT metadata #>> '{specs,color}' FROM products;       -- path as text

-- Filter rows by JSONB value
SELECT name FROM products WHERE metadata ->> 'category' = 'widgets';

-- Filter by nested value
SELECT name FROM products WHERE (metadata -> 'specs' ->> 'weight')::float > 0.5;

-- Check if key exists
SELECT name FROM products WHERE metadata ? 'stock';

-- Check if ANY key in array exists
SELECT name FROM products WHERE metadata ?| ARRAY['stock', 'rating'];

-- Check if ALL keys exist
SELECT name FROM products WHERE metadata ?& ARRAY['price', 'category'];

-- Containment: does JSONB contain this sub-object?
SELECT name FROM products WHERE metadata @> '{"category": "widgets"}';

-- Is JSONB contained by?
SELECT name FROM products WHERE '{"category": "widgets"}' <@ metadata;

JSONB Operators Quick Reference

OperatorExampleDescription
->metadata -> 'key'Get JSONB value by key
->>metadata ->> 'key'Get text value by key
#>metadata #> '{a,b}'Get JSONB by path
#>>metadata #>> '{a,b}'Get text by path
@>metadata @> '{"k":"v"}'Containment (uses GIN)
<@'{"k":"v"}' <@ metadataIs contained by
?metadata ? 'key'Key exists
?|metadata ?| ARRAY['a','b']Any key exists
?&metadata ?& ARRAY['a','b']All keys exist
||metadata || '{"new":1}'Concatenate/merge
-metadata - 'key'Remove key
#-metadata #- '{a,b}'Remove by path

Modifying JSONB Data

Update, add, and remove fields within JSONB columns without replacing the entire value.

-- Update a specific key (PostgreSQL 14+: subscript syntax)
UPDATE products
SET metadata['price'] = '19.99'
WHERE name = 'Widget A';

-- jsonb_set: update nested key (older syntax, all versions)
UPDATE products
SET metadata = jsonb_set(metadata, '{price}', '19.99')
WHERE name = 'Widget A';

-- Update nested path
UPDATE products
SET metadata = jsonb_set(metadata, '{specs,color}', '"black"')
WHERE name = 'Widget A';

-- Add a new key
UPDATE products
SET metadata = metadata || '{"discount": 0.1}'::jsonb
WHERE name = 'Widget A';

-- Remove a key
UPDATE products
SET metadata = metadata - 'stock'
WHERE name = 'Gadget B';

-- Remove multiple keys
UPDATE products
SET metadata = metadata - ARRAY['stock', 'rating'];

-- Remove nested key
UPDATE products
SET metadata = metadata #- '{specs,weight}';

Indexing JSONB for Performance

The right index strategy can make JSONB queries as fast as queries on regular columns. GIN indexes are the workhorse for JSONB.

-- GIN index: most useful for JSONB (supports ?, ?|, ?&, @>, <@)
CREATE INDEX idx_products_metadata ON products USING GIN (metadata);

-- GIN index on a specific key (for equality queries on that key)
CREATE INDEX idx_products_category ON products
    USING GIN ((metadata -> 'category'));

-- B-tree index on extracted value (for range queries)
CREATE INDEX idx_products_price ON products
    USING BTREE ((( metadata ->> 'price' )::numeric));

-- Expression index for a nested text field
CREATE INDEX idx_products_color ON products
    USING BTREE ((metadata #>> '{specs,color}'));

-- Check index usage
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM products WHERE metadata @> '{"category": "widgets"}';

Aggregation and JSONB Functions

PostgreSQL provides powerful functions for aggregating, expanding, and constructing JSONB values.

-- jsonb_agg: aggregate rows into a JSON array
SELECT
    metadata ->> 'category' AS category,
    jsonb_agg(name ORDER BY name) AS product_names,
    COUNT(*) AS count,
    AVG((metadata ->> 'price')::numeric) AS avg_price
FROM products
GROUP BY metadata ->> 'category';

-- jsonb_object_agg: build a JSON object from key-value pairs
SELECT jsonb_object_agg(name, metadata ->> 'price') AS price_map
FROM products;

-- jsonb_array_elements: expand a JSONB array into rows
SELECT
    name,
    jsonb_array_elements_text(metadata -> 'tags') AS tag
FROM products;

-- jsonb_each: expand a JSONB object into key-value rows
SELECT
    name,
    key,
    value
FROM products,
     jsonb_each(metadata -> 'specs');

-- jsonb_build_object: construct JSONB from expressions
SELECT jsonb_build_object(
    'id', id,
    'name', name,
    'price', (metadata ->> 'price')::numeric,
    'in_stock', metadata ? 'stock'
) AS product_summary
FROM products;

Performance Tips

  • Always create a GIN index on JSONB columns you query frequently. Without an index, JSONB queries require sequential scans.
  • Extract frequently-queried fields into regular columns with generated columns: GENERATED ALWAYS AS (metadata ->> 'category') STORED.
  • Use jsonb instead of json — it is faster for reads (binary format, no re-parsing) and supports indexing.
  • Avoid deep nesting. Flat JSONB structures are easier to index and query. Consider normalizing very deep structures.
  • Use @> (containment) for filtering on known sub-objects — it uses the GIN index. Avoid ->> with LIKE for text search; use full-text search instead.

Frequently Asked Questions

When should I use JSONB instead of relational columns?

Use JSONB for: semi-structured data where the schema varies per row, storing configurations or settings, event payloads or metadata, and prototype development where the schema is not yet fixed. Use regular columns when: data is highly structured and uniform, you need complex JOINs, or data changes frequently with strict transactional guarantees.

How do I search for text inside a JSONB string value?

For full-text search inside JSONB strings, use: to_tsvector('english', metadata ->> 'description') @@ to_tsquery('search term'). Create a GIN index on the tsvector expression. For simple substring matching, use metadata ->> 'field' LIKE '%pattern%' but this cannot use a GIN index.

What is the maximum size of a JSONB value?

A single JSONB value in PostgreSQL can be up to 1 GB. However, for practical performance reasons, JSONB columns work best when individual values are under 1 MB. Large JSONB values slow down updates (the entire value must be rewritten) and indexing.

How do I validate JSONB schema in PostgreSQL?

PostgreSQL 16+ supports JSON Schema validation with the jsonb_matches_schema() function. For older versions, use CHECK constraints with custom functions. Example: ALTER TABLE products ADD CONSTRAINT check_metadata CHECK (jsonb_typeof(metadata -> 'price') = 'number');

How does JSONB compare to a dedicated document database like MongoDB?

JSONB gives you document storage within PostgreSQL, combining the flexibility of document databases with ACID transactions, complex JOINs, full-text search, and all other PostgreSQL features. MongoDB offers native document-first querying and horizontal sharding. For most applications that already use PostgreSQL, JSONB is preferred over adding a separate database.

Related Tools

𝕏 Twitterin LinkedIn
この記事は役に立ちましたか?

最新情報を受け取る

毎週の開発ヒントと新ツール情報。

スパムなし。いつでも解除可能。

Try These Related Tools

{ }JSON Formatter🔷JSON to TypeScript OnlineSQLSQL Formatter

Related Articles

GraphQL vs REST API:2026年にどちらを使うべき?

GraphQLとREST APIのコード例付き徹底比較。アーキテクチャの違い、データ取得パターン、キャッシュ、選択基準を学ぶ。

APIレート制限ガイド:戦略、アルゴリズム、実装

APIレート制限完全ガイド。トークンバケット、スライディングウィンドウ、リーキーバケットアルゴリズムをコード例付きで解説。Express.jsミドルウェア、Redis分散レート制限を含む。