PostgreSQL Error 2203A: SQL JSON Member Not Found
PostgreSQL error 2203A (sql_json_member_not_found) occurs when a SQL/JSON path expression attempts to access a key or member that does not exist within a JSON object while operating in strict mode. Unlike lax mode, which silently returns an empty sequence for missing members, strict mode enforces structural integrity and raises this error immediately. This error commonly surfaces when using functions like JSON_VALUE, JSON_QUERY, or jsonb_path_query with explicit strict path expressions.
Top 3 Causes
1. Accessing a Non-Existent Key in Strict Mode
The most common cause is explicitly using strict in the JSON path expression while querying a key that doesn't exist in the target JSON object.
-- Triggers ERROR 2203A
SELECT JSON_VALUE(
'{"name": "Alice"}'::json,
'strict $.address'
);
-- Fix: Use lax mode (PostgreSQL default)
SELECT JSON_VALUE(
'{"name": "Alice"}'::json,
'lax $.address'
);
-- Returns: NULL
-- Fix: Use DEFAULT ON ERROR clause
SELECT JSON_VALUE(
'{"name": "Alice"}'::json,
'strict $.address'
DEFAULT 'N/A' ON ERROR
);
-- Returns: 'N/A'
2. Mixed JSON Schemas in the Same Column
When JSON data evolves over time, older records may lack keys that newer records have. Querying the entire table in strict mode will fail on those legacy rows.
-- Sample data with inconsistent structure
CREATE TABLE orders (id SERIAL, payload JSONB);
INSERT INTO orders (payload) VALUES
('{"item": "Book", "discount": 10}'),
('{"item": "Pen"}'); -- no 'discount' key
-- ERROR 2203A on the second row
SELECT JSON_VALUE(payload::json, 'strict $.discount')
FROM orders;
-- Fix: Check key existence before accessing
SELECT
id,
CASE
WHEN payload ? 'discount' THEN (payload->>'discount')
ELSE '0'
END AS discount
FROM orders;
-- Fix: Use jsonb_path_query_first in lax mode
SELECT
id,
jsonb_path_query_first(payload, 'lax $.discount') AS discount
FROM orders;
3. Typos or Case Mismatch in Key Names
JSON keys are case-sensitive. A path expression with even a minor case difference will fail to find the member in strict mode.
-- ERROR 2203A: wrong case
SELECT JSON_VALUE(
'{"UserName": "Bob"}'::json,
'strict $.username'
);
-- Fix: Match the exact key casing
SELECT JSON_VALUE(
'{"UserName": "Bob"}'::json,
'strict $.UserName'
);
-- Returns: 'Bob'
-- Inspect actual keys in your JSONB column
SELECT DISTINCT jsonb_object_keys(payload)
FROM orders
ORDER BY 1;
Quick Fix Solutions
-- Option 1: Switch from strict to lax mode
SELECT jsonb_path_query_first(data, 'lax $.missing_key')
FROM my_table;
-- Option 2: Use ON ERROR with a fallback value (PostgreSQL 14+)
SELECT JSON_VALUE(data::json, 'strict $.key' DEFAULT 'fallback' ON ERROR)
FROM my_table;
-- Option 3: Defensive helper function
CREATE OR REPLACE FUNCTION safe_json_get(p_data JSONB, p_key TEXT, p_default TEXT DEFAULT NULL)
RETURNS TEXT AS $$
BEGIN
RETURN COALESCE(p_data ->> p_key, p_default);
END;
$$ LANGUAGE plpgsql IMMUTABLE;
SELECT safe_json_get(data, 'address', 'Unknown') FROM my_table;
Prevention Tips
- Enforce required keys with CHECK constraints to ensure structural consistency at insert time, preventing missing-member errors at query time.
ALTER TABLE orders
ADD CONSTRAINT chk_required_fields
CHECK (payload ? 'item' AND payload ? 'discount');
- Default to lax mode and reserve strict mode for explicit validation scenarios such as data audits or integrity checks. Document this policy in your team's SQL coding standards to prevent accidental misuse of strict mode in production queries.
-- Recommended pattern: lax + COALESCE
SELECT COALESCE(
jsonb_path_query_first(payload, 'lax $.discount')::text,
'0'
) AS discount
FROM orders;
Related Errors
| Code | Name | Description |
|---|---|---|
| 2203B | sql_json_array_not_found |
Expected array element not found |
| 2203C | sql_json_number_not_found |
Expected numeric value not found |
| 2203D | sql_json_object_not_found |
Expected JSON object not found |
| 2203F | sql_json_scalar_required |
Scalar required but object/array returned |
| 22032 | invalid_json_text |
Malformed JSON input |
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)