🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

JSON Processing in SQL — Extraction, Transformation, Indexing & Aggregation

Advanced SQLSemi-Structured Data⭐ Premium

Advertisement

JSON Processing in SQL

Advanced SQL

Master JSON in SQL

JSON is the lingua franca of APIs and semi-structured data. SQL databases provide powerful operators to extract, transform, aggregate, and index JSON — turning unstructured blobs into queryable, analyzable data.

  • PostgreSQL->, ->>, @?, GIN indexes for jsonb
  • BigQueryJSON_EXTRACT_SCALAR, JSON_EXTRACT_ARRAY
  • SQL ServerJSON_VALUE, JSON_QUERY, OPENJSON
  • MySQLJSON_EXTRACT, ->>, JSON_TABLE

In interviews, JSON questions test your ability to navigate nested structures, unnest arrays, and build aggregations from semi-structured sources.


What Is JSON in SQL?

SQL databases store JSON as text with special operators for extraction, transformation, and indexing. The two main types are:

PostgreSQL: json vs jsonb

Featurejsonjsonb
StorageRaw textDecomposed binary
IndexingGIN index (slow)GIN index (fast)
OperatorsAllAll + @?, @@
PerformanceSlower2-5x faster
Duplicate keysPreservedRemoved (last wins)

Always use jsonb in PostgreSQL — it's faster for extraction and supports GIN indexing.


JSON Extraction Operators

JSON Document{'{'}"name": "John","age": 30,"address": {'{'}"city": "Boston"{'}'},"tags": ["sql", "data"]{'}'}->> (text)-> (json)@? (path)data->>'name'→ "John" (text)data->'address'→ {"city":"Boston"}data@?'$.tags[*]'→ true (exists)GIN Index on jsonb@?, @@, ? operatorsIndex: 2-5x fasterthan json type

PostgreSQL Operators

-- Basic JSON extraction
SELECT
  id,
  data->>'name' AS name,                    -- Text extraction
  data->'address'->>'city' AS city,          -- Nested extraction
  data->'preferences'->'notifications'->>'email' AS email_pref,
  (data->>'age')::INT AS age,                -- Type casting
  jsonb_array_length(data->'tags') AS tag_count
FROM users;

BigQuery Functions

-- BigQuery JSON extraction
SELECT
  id,
  JSON_EXTRACT_SCALAR(data, '$.name') AS name,
  JSON_EXTRACT_SCALAR(data, '$.address.city') AS city,
  JSON_EXTRACT(data, '$.tags') AS tags_json,
  JSON_EXTRACT_ARRAY(data, '$.orders') AS orders_array,
  ARRAY(
    SELECT JSON_EXTRACT_SCALAR(item, '$.name')
    FROM UNNEST(JSON_EXTRACT_ARRAY(data, '$.tags')) AS item
  ) AS tag_names
FROM `project.dataset.users`;

JSON Path Expressions

-- Advanced JSON path queries (PostgreSQL 12+)
SELECT
  id,
  data @? '$.orders[*]?(@.amount > 1000)' AS has_large_orders,
  jsonb_path_query_array(data, '$.tags[*]') AS all_tags,
  jsonb_path_query_first(data, '$.orders[0].items[0].name') AS first_item,
  jsonb_path_query(data, '$.orders[*]?(@.status == "pending")') AS pending_orders
FROM users;

JSON Aggregation

-- Aggregate rows INTO JSON array
SELECT
  department_id,
  jsonb_agg(
    jsonb_build_object(
      'employee_id', employee_id,
      'name', name,
      'salary', salary
    )
  ) AS employees,
  jsonb_agg(name ORDER BY salary DESC) AS names_by_salary
FROM employees
GROUP BY department_id;

-- Aggregate rows INTO JSON object (key-value map)
SELECT
  jsonb_object_agg(
    employee_id::TEXT,
    jsonb_build_object('name', name, 'salary', salary)
  ) AS employee_map
FROM employees
WHERE department_id = 1;

JSON Transformation

-- Transform JSON structure
SELECT
  id,
  jsonb_build_object(
    'full_name', data->>'first_name' || ' ' || data->>'last_name',
    'contact', jsonb_build_object(
      'email', data->>'email',
      'phone', data->'phone_numbers'->0
    ),
    'order_count', jsonb_array_length(data->'orders'),
    'total_spent', (
      SELECT SUM((item->>'amount')::NUMERIC)
      FROM jsonb_array_elements(data->'orders') AS item
    )
  ) AS transformed_data
FROM users;

JSON Array Processing

-- Unnest JSON array into rows
SELECT
  u.id,
  u.data->>'name' AS user_name,
  tag.value AS tag
FROM users u,
LATERAL jsonb_array_elements_text(u.data->'tags') AS tag(value);

-- Filter JSON array elements
SELECT
  u.id,
  jsonb_path_query_array(
    u.data->'orders',
    '$[*]?(@.amount > 100)'
  ) AS large_orders
FROM users u;

-- Aggregate JSON array values
SELECT
  u.id,
  (SELECT SUM((item->>'amount')::NUMERIC)
   FROM jsonb_array_elements(u.data->'orders') AS item
   WHERE item->>'status' = 'completed') AS total_completed
FROM users u;

JSON Merge and Patch

-- Merge two JSON objects (right overrides left)
SELECT jsonb_merge_patch(
  '{"name": "John", "age": 30}'::jsonb,
  '{"age": 31, "city": "NYC"}'::jsonb
);
-- Result: {"name": "John", "age": 31, "city": "NYC"}

-- Patch with null removal (null deletes key)
SELECT jsonb_merge_patch(
  '{"a": 1, "b": 2, "c": 3}'::jsonb,
  '{"b": null, "d": 4}'::jsonb
);
-- Result: {"a": 1, "c": 3, "d": 4}

JSON in UPDATE Statements

-- Update specific JSON path
UPDATE users
SET data = jsonb_set(data, '{address, city}', '"San Francisco"')
WHERE id = 101;

-- Add new key to JSON object
UPDATE users
SET data = data || '{"newsletter_subscribed": true}'::jsonb
WHERE data->>'status' = 'active';

-- Remove key from JSON object
UPDATE users
SET data = data - 'temp_field'
WHERE data ? 'temp_field';

JSON Indexing

-- GIN index for general JSON queries (fastest for containment)
CREATE INDEX idx_users_data ON users USING GIN (data);

-- Specific path index (B-tree on extracted value)
CREATE INDEX idx_users_email ON users ((data->>'email'));

-- Partial index for specific values
CREATE INDEX idx_active_users ON users ((data->>'status'))
WHERE data->>'status' = 'active';

⚠️

Performance Tip: Use jsonb instead of json in PostgreSQL. jsonb is stored in a decomposed binary format that supports GIN indexing and is significantly faster for queries. The json type requires re-parsing on every extraction.


Quiz: Test Your Knowledge


Follow-Up Questions

  1. What's the difference between json and jsonb in PostgreSQL?
  2. How would you index a JSON column for efficient range queries?
  3. Explain the performance implications of using JSON vs normalized tables.
  4. How do you handle schema evolution in JSON documents?
  5. What's the best approach for querying deeply nested JSON structures?

Key Takeaways

  • Use jsonb over json — binary storage + GIN indexing = 2-5x faster
  • -> returns JSON, ->> returns text — use ->> for comparison/casting
  • GIN index on jsonb for @?, @@, ? operators; B-tree for ->> equality
  • jsonb_array_elements_text unnests text arrays; jsonb_array_elements unnests objects
  • jsonb_merge_patch for combining/patching JSON documents
  • BigQuery: Use JSON_EXTRACT_SCALAR for text, JSON_EXTRACT for JSON
🔒

Premium Content

JSON Processing in SQL — Extraction, Transformation, Indexing & Aggregation

You've previewed the first section. Unlock this full lesson and 900+ advanced tutorials with a Premium plan.

🎯End-to-end Projects
💼Interview Prep
📜Certificates
🤝Community Access

Already a member? Log in

Advertisement