JSON functions
Check, read, build and change JSON values, like company profiles and report numbers.
- Category: Functions — JSON and bytes
- Keywords: JSON, JSONB, json path, $., [last], nested data, document, profile, settings, key, array, object, ->, ->>, @>, <@, ?, ?|, ?&, key exists, GIN, USING GIN, JSON index, jsonpath, SQL/JSON path, jsonb_path_exists, jsonb_path_match, jsonb_path_query, jsonb_path_query_array, jsonb_path_query_first, @?, @@, filter JSON, like_regex, keyvalue, jsonb_pretty, pretty JSON, jsonb_strip_nulls, json_strip_nulls, remove nulls, jsonb_set_lax, jsonb_insert, jsonb_object, array_to_json, get key, change JSON, build JSON, delete key, #-, remove key, JSON to rows, jsonb_each, json_each, jsonb_each_text, jsonb_array_elements, json_array_elements, jsonb_array_elements_text, jsonb_object_keys, json_to_record, json_to_recordset, jsonb_populate_record, jsonb_populate_recordset, bulk insert from JSON
- See also: Bytes, hex and base64, Column types, UPDATE and DELETE, Conversion functions, Text functions
JSON is a text format for data with names and lists: {"branches": 270, "tags": ["bank"]}.
Some data is different for every row. A bank has branches; a hydropower company has megawatts.
Put such data in a JSON column, and read it with the functions on this page.
- A path says which part you want:
'$.office.city'or'$.tags[0]'(see JSON paths). - A path that is not in the document gives
NULL. It is not an error. - Most functions also take text that holds JSON. KDB checks the text first; bad JSON is an error.
- A
NULLargument givesNULL(exceptjson_objectandjson_array, which turnNULLinto JSONnull). - The result tables show a JSON value after reading it: a JSON string shows without its quotes, JSON
nullshows asNULL, and2.50can show as2.5. HTTP answers in JSON do the same. To see the exact saved text, add::STRING(some examples do).
Every example on this page has two small tables, companies and fundamentals (open "Setup" under an example to see them).
Chapter 18 of the tutorial shows the same tables step by step.
Setup used by the examples below:
CREATE TABLE companies (
symbol STRING PRIMARY KEY,
name STRING NOT NULL,
sector STRING NOT NULL,
profile JSON DEFAULT '{}'
);
INSERT INTO companies VALUES
('CITY', 'City Bank Limited', 'Banking',
'{"website": "https://www.citybank.example.com", "office": {"city": "Kathmandu", "phone": "01-5970015"}, "branches": 270, "tags": ["bank", "A class"]}'),
('COAST', 'Coastal Bank Limited', 'Banking',
'{"website": "https://www.coastalbank.example.com", "office": {"city": "Kathmandu"}, "branches": 380, "tags": ["bank"]}'),
('VOLT', 'Volt River Hydropower Limited', 'Energy',
'{"website": "https://www.voltriver.example.com", "capacity_mw": 456, "rivers": ["Volt River"], "name_ne": "भोल्ट रिभर"}');
CREATE TABLE fundamentals (
id LONG PRIMARY KEY AUTO,
symbol STRING NOT NULL,
fiscal_year STRING NOT NULL,
quarter INT NOT NULL,
eps DECIMAL(2),
extra JSON DEFAULT '{}'
);
INSERT INTO fundamentals (symbol, fiscal_year, quarter, eps, extra) VALUES
('CITY', '2082/83', 3, 11.40, '{"npl_pct": 2.75, "car_pct": 13.1, "deposits": 442900000000}'),
('CITY', '2082/83', 4, 12.10, '{"npl_pct": 2.60, "car_pct": 13.4, "deposits": 455000000000, "audited": false}'),
('COAST', '2082/83', 4, 6.35, '{"npl_pct": 4.10, "car_pct": 12.2}'),
('VOLT', '2082/83', 4, 3.20, '{"energy_gwh": 2105.4, "plant_factor_pct": 52.7}');
Example: Read the company profiles
SELECT symbol,
json_value(profile, '$.office.city') AS city,
profile ->> 'website' AS website,
json_value(profile, '$.branches') AS branches,
profile -> 'rivers' ->> 0 AS river
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | city (STRING) | website (STRING) | branches (LONG) | river (STRING) |
|---|---|---|---|---|
| CITY | Kathmandu | https://www.citybank.example.com | 270 | NULL |
| COAST | Kathmandu | https://www.coastalbank.example.com | 380 | NULL |
| VOLT | NULL | https://www.voltriver.example.com | NULL | Volt River |
(3 rows)
JSON column type
Syntax:
column_name JSON [DEFAULT '{}']
column_name JSONB
A JSON column keeps checked JSON. JSONB is another name for the same type. The column types topic lists all types.
- Check: KDB refuses text that is not valid JSON. The error says the row, the column and the character.
- Spaces: spaces between the parts are removed.
{ "a" : 1 }is saved as{"a":1}. - Order: the order of the keys stays. The digits of numbers stay (
2.60stays2.60). - Values: a number,
TRUEorFALSEput into aJSONcolumn becomes JSON (5,true). Text must be JSON text. - NULL: SQL
NULLis "no value". The JSON text'null'is a value:profile IS NULLis false for it. - Size: at most 16 MB per value, and at most 256 levels of
{ }and[ ]inside each other. - Compare: two JSON values are equal when their text (without spaces) is equal. So
{"a":1,"b":2}is not equal to{"b":2,"a":1}. A JSON number compared with a SQL number is compared by value. - Notes:
ORDER BY,minandmaxon JSON values compare the JSON text, so10comes before9. To sort by a number inside JSON, usejson_value_double(...)orjson_value(...).
Example: Spaces are removed; SQL NULL and JSON null are different
Setup:
CREATE TABLE app_settings (user_id LONG PRIMARY KEY, settings JSON);
INSERT INTO app_settings VALUES
(1, '{ "theme" : "dark", "lang" : "ne" }'),
(2, 'null'),
(3, NULL),
(4, 5);
SELECT user_id, settings::STRING AS saved_text, settings IS NULL AS is_sql_null, json_type(settings) AS kind
FROM app_settings
ORDER BY user_id;
Result:
| user_id (LONG) | saved_text (STRING) | is_sql_null (BOOL) | kind (STRING) |
|---|---|---|---|
| 1 | {"theme":"dark","lang":"ne"} | false | object |
| 2 | null | false | null |
| 3 | NULL | true | NULL |
| 4 | 5 | false | number |
(4 rows)
Example: Bad JSON is refused (shows an error)
INSERT INTO fundamentals (symbol, fiscal_year, quarter, extra)
VALUES ('VOLT', '2082/83', 3, '{"energy_gwh": 1850.2,}');
Result:
Error: column 'extra' (JSON): not valid JSON: a JSON object cannot end with ',' at character 23 (near '}')
Example: Most read news: sort by the number, not by the JSON
Setup:
CREATE TABLE news (id LONG PRIMARY KEY, title STRING, stats JSON);
INSERT INTO news VALUES
(1, 'Market index gains 12 points', '{"views": 9}'),
(2, 'City Bank profit rises 18%', '{"views": 120}'),
(3, 'Volt River output up', '{"views": 10}');
SELECT id, title, stats -> 'views' AS views FROM news ORDER BY stats -> 'views' DESC;
SELECT id, title, json_value_long(stats, '$.views') AS views FROM news ORDER BY views DESC;
Result:
SELECT id, title, stats -> 'views' AS views FROM news ORDER BY stats -> 'views' DESC
| id (LONG) | title (STRING) | views (JSON) |
|---|---|---|
| 2 | City Bank profit rises 18% | 120 |
| 3 | Volt River output up | 10 |
| 1 | Market index gains 12 points | 9 |
(3 rows)
SELECT id, title, json_value_long(stats, '$.views') AS views FROM news ORDER BY views DESC
| id (LONG) | title (STRING) | views (LONG) |
|---|---|---|
| 2 | City Bank profit rises 18% | 120 |
| 3 | Volt River output up | 10 |
| 1 | Market index gains 12 points | 9 |
(3 rows)
JSON paths
Syntax:
'$' the whole document
'$.key' a key of an object
'$.key.inner' a key inside a key
'$.list[0]' element 0 of an array (the first one)
'$.list[-1]' the last element ([-2] is the one before it)
'$.list[last]' the last element, too
'$."key with spaces"' a key with spaces, dots or other signs
'$["key"]' '$[''key'']' the same, in brackets
A path is text that points to one part of a JSON value. It always starts with $. It is used by json_extract, json_value, json_set and the other functions with a path argument.
| Part | Meaning |
|---|---|
$ | the whole document |
.name | the key name of an object |
[n] | element n of an array; 0 is the first |
[-n], [last] | counted from the end; [-1] and [last] are the last element |
."...", ["..."], ['...'] | a key written in quotes |
- Missing: a key or element that is not there gives
NULL. A number on an object, or a key on an array, also givesNULL. - Notes: a key written twice in one object: the path finds the last one.
- Not supported: wildcards (
$.items[*],$.*), ranges, filters and... They give an error.[-0]is an error (the last element is[-1]). PostgreSQL's full path language, with all of these, is in jsonb_path_query and @?. - A path must be text in quotes. A column or a number in its place is an error.
Example: Different paths on one company profile
SELECT json_extract(profile, '$.office.city') AS city,
json_extract(profile, '$.tags[0]') AS first_tag,
json_extract(profile, '$.tags[-1]') AS last_tag,
json_extract(profile, '$.tags[last]') AS also_last,
json_extract(profile, '$.tags[5]') AS no_such_tag
FROM companies
WHERE symbol = 'CITY';
Result:
| city (JSON) | first_tag (JSON) | last_tag (JSON) | also_last (JSON) | no_such_tag (STRING) |
|---|---|---|---|---|
| "Kathmandu" | "bank" | "A class" | "A class" | NULL |
(1 row)
Example: Keys with spaces or dots need quotes
SELECT json_extract('{"net profit": 3270.1, "q4.audited": true}', '$."net profit"') AS a,
json_extract('{"net profit": 3270.1, "q4.audited": true}', '$["q4.audited"]') AS b,
json_extract('{"net profit": 3270.1, "q4.audited": true}', '$.q4.audited') AS without_quotes;
Result:
| a (JSON) | b (JSON) | without_quotes (STRING) |
|---|---|---|
| 3270.1 | true | NULL |
(1 row)
Example: Wildcards are not supported (shows an error)
SELECT json_extract(profile, '$.tags[*]') FROM companies;
Result:
Error: line 1, column 8: json_extract: JSON path '$.tags[*]' is not valid: wildcards (*) are not supported (paths look like '$.company.sector', '$.items[0]' or '$."key with spaces"')
Example: A path must start with $ (shows an error)
SELECT json_extract(profile, 'office.city') FROM companies;
Result:
Error: line 1, column 8: json_extract: JSON path 'office.city' is not valid: it must start with $ (paths look like '$.company.sector', '$.items[0]' or '$."key with spaces"')
json
Syntax:
json(text)
Checks that text is valid JSON and gives it as a JSON value, without extra spaces. CAST(text AS JSON) and text::JSON do the same.
| Argument | Type | Meaning |
|---|---|---|
text | STRING (or JSON) | JSON text, like '{"a": 1}' |
- Returns:
JSON. - NULL:
json(NULL)isNULL.json('null')is the JSON valuenull. - Notes: bad JSON is an error that says what is wrong and at which character. Keys and strings need double quotes (
"a"), andtrue,false,nullare lower case. - Use
json(...)when you put JSON inside other JSON withjson_object,json_arrayorjson_set. Without it, the text is saved as a JSON string.
Example
SELECT json('{ "symbol" : "CITY", "close" : 512.50 }')::STRING AS saved_text,
json('[1, 2, 3]') AS list,
CAST('{"a": 1}' AS JSON) AS with_cast,
json('{"name_ne": "सिटी बैंक"}') AS nepali;
Result:
| saved_text (STRING) | list (JSON) | with_cast (JSON) | nepali (JSON) |
|---|---|---|---|
| {"symbol":"CITY","close":512.50} | [1,2,3] | {"a":1} | {"name_ne":"सिटी बैंक"} |
(1 row)
Example: Single quotes are not JSON (shows an error)
SELECT json('{''symbol'': ''CITY''}');
Result:
Error: line 1, column 8: json: argument 1 is not valid JSON: expected a key in double quotes at character 2 (near ''symbol': 'C...')
Example: true, false and null are lower case (shows an error)
SELECT json('{"listed": TRUE}');
Result:
Error: line 1, column 8: json: argument 1 is not valid JSON: expected a JSON value (true, false and null are lower case) at character 12 (near 'TRUE}')
to_json
Syntax:
to_json(value)
Turns one SQL value into JSON. Text becomes a JSON string with " around it. Numbers and TRUE/FALSE stay as they are.
| Argument | Type | Meaning |
|---|---|---|
value | any type | the value to turn into JSON |
- Returns:
JSON. - NULL:
to_json(NULL)is SQLNULL(not JSONnull). - Notes: a
DECIMALkeeps its digits (2.50); aDOUBLEdoes not (2.5). A time becomes a string like"2026-09-10T05:15:00.000Z". AJSONvalue stays the same.BYTESbecome a string like"\\x0a0b". to_json('{"a": 1}')gives a JSON string, not an object. Usejson(...)to read JSON text.
Example
SELECT to_json('City "Bank"')::STRING AS text_value,
to_json(512.50)::STRING AS double_value,
to_json(CAST(2.50 AS DECIMAL(2)))::STRING AS exact,
to_json(TRUE)::STRING AS yes,
to_json(CAST('2026-09-10T05:15:00Z' AS TIMESTAMP))::STRING AS time_value,
to_json('{"a": 1}')::STRING AS still_text;
Result:
| text_value (STRING) | double_value (STRING) | exact (STRING) | yes (STRING) | time_value (STRING) | still_text (STRING) |
|---|---|---|---|---|---|
| "City \"Bank\"" | 512.5 | 2.50 | true | "2026-09-10T05:15:00.000Z" | "{\"a\": 1}" |
(1 row)
json_valid
Syntax:
json_valid(text)
true when text is valid JSON, else false. It never gives an error, so it is good for checking data before you insert it.
| Argument | Type | Meaning |
|---|---|---|
text | STRING (or any value) | the text to check |
- Returns:
BOOL. - NULL:
json_valid(NULL)isNULL. - Notes: a number,
TRUEor aJSONvalue is valid.BYTESare never valid. Empty text is not valid.
Example
SELECT json_valid('{"npl_pct": 2.75}') AS good,
json_valid('{"npl_pct": 2.75,}') AS extra_comma,
json_valid('{npl_pct: 2.75}') AS no_quotes,
json_valid('') AS empty,
json_valid(NULL) AS nothing;
Result:
| good (BOOL) | extra_comma (BOOL) | no_quotes (BOOL) | empty (BOOL) | nothing (STRING) |
|---|---|---|---|---|
| true | false | false | false | NULL |
(1 row)
Example: Find bad rows in imported text before loading them
Setup:
CREATE TABLE import_raw (line_no LONG PRIMARY KEY, symbol STRING, extra_text STRING);
INSERT INTO import_raw VALUES
(1, 'CITY', '{"npl_pct": 2.91}'),
(2, 'COAST', '{"npl_pct": 4.3,}'),
(3, 'VOLT', '{"energy_gwh": 1850.2}');
SELECT line_no, symbol, extra_text
FROM import_raw
WHERE NOT json_valid(extra_text);
Result:
| line_no (LONG) | symbol (STRING) | extra_text (STRING) |
|---|---|---|
| 2 | COAST | {"npl_pct": 4.3,} |
(1 row)
json_extract
Syntax:
json_extract(doc, path)
The part of doc at path, as JSON. A string stays in quotes ("Kathmandu"), and objects and arrays stay whole.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | a JSON path like '$.office.city' |
- Returns:
JSON. - NULL:
NULLwhen an argument isNULL, or the path is not in the document. - Notes:
doc -> 'key'is a short way to writejson_extract(doc, '$.key')(see ->, ->>). To get a plain SQL value (text without quotes, a number), usejson_value.
Example
SELECT symbol,
json_extract(profile, '$.office') AS office,
json_extract(profile, '$.office.city')::STRING AS city_json_text,
json_value(profile, '$.office.city') AS city_value,
json_extract(profile, '$.tags') AS tags
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | office (JSON) | city_json_text (STRING) | city_value (STRING) | tags (JSON) |
|---|---|---|---|---|
| CITY | {"city":"Kathmandu","phone":"01-5970015"} | "Kathmandu" | Kathmandu | ["bank","A class"] |
| COAST | {"city":"Kathmandu"} | "Kathmandu" | Kathmandu | ["bank"] |
| VOLT | NULL | NULL | NULL | NULL |
(3 rows)
Example: The text must be valid JSON (shows an error)
SELECT json_extract('CITY', '$.a');
Result:
Error: line 1, column 8: json_extract: argument 1 is not valid JSON: expected a JSON value (an object, array, string, number, true, false or null) at character 1 (near 'CITY')
json_value
Syntax:
json_value(doc, path)
The part of doc at path as a normal SQL value. The type depends on the JSON value:
| JSON value | SQL result |
|---|---|
"text" | STRING without quotes |
a whole number like 270 | LONG |
a number with a point or e, like 2.60 | DOUBLE (2.6) |
true, false | BOOL |
null, an object, an array | NULL |
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | a JSON path |
- Returns:
STRING,LONG,DOUBLEorBOOL;NULLwhen the path is missing. - NULL:
NULLwhen an argument isNULL. - Notes: a whole number too big for
LONGcomes back asDOUBLE. The result type can change from row to row. When you need one type, usejson_value_text,json_value_double,json_value_longorjson_value_bool. - A
DOUBLEis not exact. For money, take the text and cast it:CAST(json_value_text(doc, '$.price') AS DECIMAL(2)).
Example
SELECT json_value(profile, '$.office.city') AS city,
json_value(profile, '$.branches') AS branches,
json_value(profile, '$.tags') AS tags_is_array,
json_value(profile, '$.no_such_key') AS missing
FROM companies
WHERE symbol = 'CITY';
Result:
| city (STRING) | branches (LONG) | tags_is_array (STRING) | missing (STRING) |
|---|---|---|---|
| Kathmandu | 270 | NULL | NULL |
(1 row)
Example: Banks with bad loans (NPL) above 3 percent
SELECT f.symbol, c.name, json_value(f.extra, '$.npl_pct') AS npl_pct
FROM fundamentals f
JOIN companies c ON c.symbol = f.symbol
WHERE f.quarter = 4 AND json_value(f.extra, '$.npl_pct') > 3;
Result:
| symbol (STRING) | name (STRING) | npl_pct (DOUBLE) |
|---|---|---|
| COAST | Coastal Bank Limited | 4.1 |
(1 row)
Example: Nepali text inside JSON: letters count as one
SELECT json_value(profile, '$.name_ne') AS name_ne,
length(json_value(profile, '$.name_ne')) AS letters
FROM companies
WHERE symbol = 'VOLT';
Result:
| name_ne (STRING) | letters (LONG) |
|---|---|
| भोल्ट रिभर | 10 |
(1 row)
json_value_text
Syntax:
json_value_text(doc, path)
The part at path always as text. A JSON string comes without quotes. A number keeps its digits as written (2.60). An object or array comes as its JSON text.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | a JSON path |
- Returns:
STRING. - NULL:
NULLwhen an argument isNULL, the path is missing, or the value is JSONnull. - Notes: this is the safe way to read exact numbers:
CAST(json_value_text(...) AS DECIMAL(2)).doc ->> 'key'gives the same text for one key.
Example: Exact percent values for a report
SELECT symbol, quarter,
json_value_text(extra, '$.car_pct') AS car_text,
CAST(json_value_text(extra, '$.npl_pct') AS DECIMAL(2)) AS npl_exact,
json_value(extra, '$.npl_pct') AS npl_double
FROM fundamentals
WHERE symbol IN ('CITY', 'COAST')
ORDER BY symbol, quarter;
Result:
| symbol (STRING) | quarter (INT) | car_text (STRING) | npl_exact (DECIMAL(2)) | npl_double (DOUBLE) |
|---|---|---|---|---|
| CITY | 3 | 13.1 | 2.75 | 2.75 |
| CITY | 4 | 13.4 | 2.60 | 2.6 |
| COAST | 4 | 12.2 | 4.10 | 4.1 |
(3 rows)
Example
SELECT json_value_text(profile, '$.office') AS office_json,
json_value_text(profile, '$.branches') AS branches_text
FROM companies
WHERE symbol = 'CITY';
Result:
| office_json (STRING) | branches_text (STRING) |
|---|---|
| {"city":"Kathmandu","phone":"01-5970015"} | 270 |
(1 row)
json_value_double
Syntax:
json_value_double(doc, path)
The part at path as a DOUBLE number. A JSON number works, and so does a JSON string that holds a number ("12.5").
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | a JSON path |
- Returns:
DOUBLE. - NULL:
NULLwhen an argument isNULL, the path is missing, or the value is not a number ("abc",true, an object). - Notes: it never gives an error for a wrong value, so it is safe in
WHEREandORDER BY.
Example
SELECT json_value_double('{"close": 512.5}', '$.close') AS number,
json_value_double('{"close": "512.5"}', '$.close') AS number_in_text,
json_value_double('{"close": "n/a"}', '$.close') AS not_a_number,
json_value_double('{"close": true}', '$.close') AS bool_value;
Result:
| number (DOUBLE) | number_in_text (DOUBLE) | not_a_number (STRING) | bool_value (STRING) |
|---|---|---|---|
| 512.5 | 512.5 | NULL | NULL |
(1 row)
Example: Hydropower plants with a plant factor above 50 percent
SELECT symbol, json_value_double(extra, '$.plant_factor_pct') AS plant_factor
FROM fundamentals
WHERE json_value_double(extra, '$.plant_factor_pct') > 50;
Result:
| symbol (STRING) | plant_factor (DOUBLE) |
|---|---|
| VOLT | 52.7 |
(1 row)
json_value_long
Syntax:
json_value_long(doc, path)
The part at path as a LONG (a whole number). 12 and 12.0 work, and so does a string like "42". 12.5 does not fit and gives NULL.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | a JSON path |
- Returns:
LONG. - NULL:
NULLwhen an argument isNULL, the path is missing, or the value is not a whole number (or too big forLONG).
Example
SELECT json_value_long('{"branches": 270}', '$.branches') AS a,
json_value_long('{"branches": 270.0}', '$.branches') AS b,
json_value_long('{"branches": "270"}', '$.branches') AS c,
json_value_long('{"branches": 270.5}', '$.branches') AS d,
json_value_long('{"branches": 99999999999999999999}', '$.branches') AS too_big;
Result:
| a (LONG) | b (LONG) | c (LONG) | d (STRING) | too_big (STRING) |
|---|---|---|---|---|
| 270 | 270 | 270 | NULL | NULL |
(1 row)
Example: Total branches of all banks
SELECT sum(json_value_long(profile, '$.branches')) AS branches
FROM companies
WHERE sector = 'Banking';
Result:
| branches (LONG) |
|---|
| 650 |
(1 row)
json_value_bool
Syntax:
json_value_bool(doc, path)
The part at path as BOOL. JSON true and false work, and so do the strings "true" and "false".
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | a JSON path |
- Returns:
BOOL. - NULL:
NULLwhen an argument isNULL, the path is missing, or the value is something else (1,"yes", an object).
Example
SELECT json_value_bool('{"audited": true}', '$.audited') AS a,
json_value_bool('{"audited": "false"}', '$.audited') AS b,
json_value_bool('{"audited": 1}', '$.audited') AS c,
json_value_bool('{"audited": "yes"}', '$.audited') AS d;
Result:
| a (BOOL) | b (BOOL) | c (STRING) | d (STRING) |
|---|---|---|---|
| true | false | NULL | NULL |
(1 row)
Example: Quarter results that are not audited yet
SELECT symbol, fiscal_year, quarter
FROM fundamentals
WHERE json_value_bool(extra, '$.audited') = FALSE;
Result:
| symbol (STRING) | fiscal_year (STRING) | quarter (INT) |
|---|---|---|
| CITY | 2082/83 | 4 |
(1 row)
json_type
Syntax:
json_type(doc)
json_type(doc, path)
The kind of JSON value: object, array, string, number, boolean or null.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | optional JSON path; default '$' (the whole document) |
- Returns:
STRING. - NULL: SQL
NULLwhen an argument isNULLor the path is missing. For a JSONnullvalue it gives the text'null'. - Notes: use it to tell "the key is missing" (
NULL) from "the key is there withnull" ('null').
Example
SELECT json_type(profile) AS whole,
json_type(profile, '$.office') AS office,
json_type(profile, '$.tags') AS tags,
json_type(profile, '$.branches') AS branches,
json_type(profile, '$.website') AS website,
json_type(profile, '$.capacity_mw') AS missing
FROM companies
WHERE symbol = 'CITY';
Result:
| whole (STRING) | office (STRING) | tags (STRING) | branches (STRING) | website (STRING) | missing (STRING) |
|---|---|---|---|---|---|
| object | object | array | number | string | NULL |
(1 row)
Example: Which quarters have the audited key at all
SELECT symbol, quarter, json_type(extra, '$.audited') AS audited_type
FROM fundamentals
ORDER BY symbol, quarter;
Result:
| symbol (STRING) | quarter (INT) | audited_type (STRING) |
|---|---|---|
| CITY | 3 | NULL |
| CITY | 4 | boolean |
| COAST | 4 | NULL |
| VOLT | 4 | NULL |
(4 rows)
json_array_length
Syntax:
json_array_length(doc)
json_array_length(doc, path)
The number of elements in a JSON array. Elements inside inner arrays are not counted.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | optional JSON path to the array; default '$' |
- Returns:
LONG. - NULL:
NULLwhen an argument isNULL, the path is missing, or the value is not an array.
Example
SELECT symbol,
json_array_length(profile, '$.tags') AS tags,
json_array_length(profile, '$.rivers') AS rivers
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | tags (LONG) | rivers (LONG) |
|---|---|---|
| CITY | 2 | NULL |
| COAST | 1 | NULL |
| VOLT | NULL | 1 |
(3 rows)
Example
SELECT json_array_length('[1, [2, 3], {"a": [4]}]') AS top_level_only,
json_array_length('[]') AS empty,
json_array_length('{"a": 1}') AS not_an_array;
Result:
| top_level_only (LONG) | empty (LONG) | not_an_array (STRING) |
|---|---|---|
| 3 | 0 | NULL |
(1 row)
json_keys
Syntax:
json_keys(doc)
json_keys(doc, path)
The keys of a JSON object, as a JSON array of strings, in the order they are written.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document |
path | STRING | optional JSON path to the object; default '$' |
- Returns:
JSON(an array). - NULL:
NULLwhen an argument isNULL, the path is missing, or the value is not an object. - Notes: a key written twice is listed once.
Example: Which extra numbers each company reports
SELECT symbol, quarter, json_keys(extra) AS keys
FROM fundamentals
ORDER BY symbol, quarter;
Result:
| symbol (STRING) | quarter (INT) | keys (JSON) |
|---|---|---|
| CITY | 3 | ["npl_pct","car_pct","deposits"] |
| CITY | 4 | ["npl_pct","car_pct","deposits","audited"] |
| COAST | 4 | ["npl_pct","car_pct"] |
| VOLT | 4 | ["energy_gwh","plant_factor_pct"] |
(4 rows)
Example
SELECT json_keys(profile, '$.office') AS office_keys,
json_array_length(json_keys(profile)) AS number_of_keys
FROM companies
WHERE symbol = 'CITY';
Result:
| office_keys (JSON) | number_of_keys (LONG) |
|---|---|
| ["city","phone"] | 4 |
(1 row)
json_object
Syntax:
json_object(key, value [, key, value ...])
Builds a JSON object from pairs of key and value. Text values become JSON strings; numbers, TRUE/FALSE and JSON values are put in as they are.
| Argument | Type | Meaning |
|---|---|---|
key | STRING | the name of the key (a number is turned into text) |
value | any type | the value; NULL becomes JSON null |
- Returns:
JSON.json_object()with no arguments is{}. - NULL: a
NULLvalue becomesnull. ANULLkey is an error. - Notes: an odd number of arguments is an error. When a key is written twice, the last value wins. At most 64 arguments.
- To put JSON inside, pass a
JSONvalue (a JSON column, orjson('...')). Plain text like'["bank"]'becomes a string.
Example: A JSON answer for an app, made from table columns
SELECT json_object('symbol', symbol,
'name', name,
'city', json_value(profile, '$.office.city'),
'tags', profile -> 'tags',
'listed', TRUE) AS api_answer
FROM companies
WHERE symbol = 'COAST';
Result:
| api_answer (JSON) |
|---|
| {"symbol":"COAST","name":"Coastal Bank Limited","city":"Kathmandu","tags":["bank"],"listed":true} |
(1 row)
Example: Text is not JSON until you use json()
SELECT json_object('tags', '["bank"]') AS as_text,
json_object('tags', json('["bank"]')) AS as_json,
json_object('note', NULL, 'note', 'second') AS last_wins;
Result:
| as_text (JSON) | as_json (JSON) | last_wins (JSON) |
|---|---|---|
| {"tags":"[\"bank\"]"} | {"tags":["bank"]} | {"note":"second"} |
(1 row)
Example: Keys and values must come in pairs (shows an error)
SELECT json_object('symbol', 'CITY', 'price');
Result:
Error: line 1, column 8: json_object: needs pairs of key and value, like json_object('symbol', 'CITY', 'price', 512.5)
json_array
Syntax:
json_array([value, value ...])
Builds a JSON array from the values, in order.
| Argument | Type | Meaning |
|---|---|---|
value | any type | an element; NULL becomes JSON null |
- Returns:
JSON.json_array()with no arguments is[]. - NULL: a
NULLvalue becomesnull. - Notes: text becomes a JSON string; use
json('...')to put an object or array inside. At most 64 values.
Example
SELECT json_array('CITY', 512.5, TRUE, NULL) AS mixed,
json_array(json('{"q": 3}'), json('{"q": 4}')) AS objects,
json_array() AS empty;
Result:
| mixed (JSON) | objects (JSON) | empty (JSON) |
|---|---|---|
| ["CITY",512.5,true,null] | [{"q":3},{"q":4}] | [] |
(1 row)
Example: Open, high, low and close of one day as a small array (sample data: sample)
SELECT time, json_array(open, high, low, close) AS ohlc
FROM ochl_day
WHERE symbol = 'CITY'
LIMIT 3;
Result:
| time (TIMESTAMP) | ohlc (JSON) |
|---|---|
| 2025-12-28T00:00:00.000Z | [540.3,542.5,539.2,542.2] |
| 2025-12-29T00:00:00.000Z | [540.6,542.1,538.8,539.7] |
| 2025-12-30T00:00:00.000Z | [538.5,540.5,536.4,539.3] |
(3 rows)
json_set
Syntax:
json_set(doc, path, value [, path, value ...])
Changes doc: puts value at path. A key that is there gets the new value; a key that is not there is added. The other keys stay. Use it in UPDATE.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document to change |
path | STRING | a JSON path |
value | any type | the new value (text becomes a JSON string; use json('...') for JSON) |
- Returns:
JSON(the changed document; the table is changed only byUPDATE). - NULL:
NULLwhendocor apathisNULL. ANULLvalue puts JSONnull. - Notes: when the parent of the path is not there (
$.a.bwithouta), nothing changes and there is no error. An array index after the end ($.tags[9]) adds at the end. More pairs are done one after the other. - Differences: works like
JSON_SETin MySQL. PostgreSQL'sjsonb_setis different (paths like'{a,b}').
Example: Mark a quarter as audited and add the auditor
Setup:
UPDATE fundamentals
SET extra = json_set(extra, '$.audited', TRUE, '$.auditor', 'CA firm A')
WHERE symbol = 'CITY' AND quarter = 4;
SELECT extra::STRING AS extra FROM fundamentals WHERE symbol = 'CITY' AND quarter = 4;
Result:
| extra (STRING) |
|---|
| {"npl_pct":2.60,"car_pct":13.4,"deposits":455000000000,"audited":true,"auditor":"CA firm A"} |
(1 row)
Example: Change, add, and a missing parent
SELECT json_set('{"theme": "light"}', '$.theme', 'dark') AS changed,
json_set('{"theme": "light"}', '$.lang', 'ne') AS added,
json_set('{"theme": "light"}', '$.notify.email', FALSE) AS no_parent,
json_set('{"tags": ["bank"]}', '$.tags[9]', 'A class') AS array_end;
Result:
| changed (JSON) | added (JSON) | no_parent (JSON) | array_end (JSON) |
|---|---|---|---|
| {"theme":"dark"} | {"theme":"light","lang":"ne"} | {"theme":"light"} | {"tags":["bank","A class"]} |
(1 row)
Example: Paths and values must come in pairs (shows an error)
SELECT json_set('{"a": 1}', '$.a', 2, '$.b');
Result:
Error: line 1, column 8: json_set: needs pairs of path and value after the document, like json_set(doc, '$.price', 512.5)
json_insert
Syntax:
json_insert(doc, path, value [, path, value ...])
Like json_set, but it only adds. A key or array element that is already there keeps its old value.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document to change |
path | STRING | a JSON path |
value | any type | the value to add |
- Returns:
JSON. - NULL:
NULLwhendocor apathisNULL. ANULLvalue adds JSONnull. - Notes: good for default values: "set it only when it is not set yet".
Example: Add a default language, but do not change a user's choice
SELECT json_insert('{"theme": "dark"}', '$.lang', 'en') AS new_key,
json_insert('{"theme": "dark", "lang": "ne"}', '$.lang', 'en') AS kept,
json_insert('{"tags": ["bank"]}', '$.tags[1]', 'A class') AS array_end;
Result:
| new_key (JSON) | kept (JSON) | array_end (JSON) |
|---|---|---|
| {"theme":"dark","lang":"en"} | {"theme":"dark","lang":"ne"} | {"tags":["bank","A class"]} |
(1 row)
json_replace
Syntax:
json_replace(doc, path, value [, path, value ...])
Like json_set, but it only changes what is there. A missing key is not added.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document to change |
path | STRING | a JSON path |
value | any type | the new value |
- Returns:
JSON. - NULL:
NULLwhendocor apathisNULL. ANULLvalue puts JSONnull.
Example
SELECT json_replace('{"branches": 270}', '$.branches', 272) AS changed,
json_replace('{"branches": 270}', '$.atms', 400) AS not_added;
Result:
| changed (JSON) | not_added (JSON) |
|---|---|
| {"branches":272} | {"branches":270} |
(1 row)
Example: Update the phone only for companies that have one
Setup:
UPDATE companies
SET profile = json_replace(profile, '$.office.phone', '01-5970099');
SELECT symbol, profile -> 'office' AS office FROM companies ORDER BY symbol;
Result:
| symbol (STRING) | office (JSON) |
|---|---|
| CITY | {"city":"Kathmandu","phone":"01-5970099"} |
| COAST | {"city":"Kathmandu"} |
| VOLT | NULL |
(3 rows)
json_remove
Syntax:
json_remove(doc, path [, path ...])
Removes the keys or array elements at the paths.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document to change |
path | STRING | a JSON path; up to 63 paths |
- Returns:
JSON. - NULL:
NULLwhendocor apathisNULL. - Notes: a path that is not there changes nothing. Paths are done one after the other, so after removing
[0]the next element becomes[0]. Removing'$'(the whole document) is an error.
Example
SELECT json_remove('{"npl_pct": 2.6, "car_pct": 13.4, "draft": true}', '$.draft') AS one_key,
json_remove('{"tags": ["bank", "old", "A class"]}', '$.tags[1]') AS one_element,
json_remove('{"a": 1}', '$.no_such_key') AS nothing_to_remove;
Result:
| one_key (JSON) | one_element (JSON) | nothing_to_remove (JSON) |
|---|---|---|
| {"npl_pct":2.6,"car_pct":13.4} | {"tags":["bank","A class"]} | {"a":1} |
(1 row)
Example: Remove a key from every row
Setup:
UPDATE fundamentals SET extra = json_remove(extra, '$.deposits');
SELECT symbol, quarter, extra::STRING AS extra FROM fundamentals ORDER BY symbol, quarter;
Result:
| symbol (STRING) | quarter (INT) | extra (STRING) |
|---|---|---|
| CITY | 3 | {"npl_pct":2.75,"car_pct":13.1} |
| CITY | 4 | {"npl_pct":2.60,"car_pct":13.4,"audited":false} |
| COAST | 4 | {"npl_pct":4.10,"car_pct":12.2} |
| VOLT | 4 | {"energy_gwh":2105.4,"plant_factor_pct":52.7} |
(4 rows)
Example: The whole document cannot be removed (shows an error)
SELECT json_remove('{"a": 1}', '$');
Result:
Error: line 1, column 8: json_remove: cannot remove the whole JSON document ($)
json_array_append
Syntax:
json_array_append(doc, path, value)
Adds value at the end of the array at path.
| Argument | Type | Meaning |
|---|---|---|
doc | JSON or JSON text | the document to change |
path | STRING | a JSON path to an array ('$' when doc itself is the array) |
value | any type | the new element; use json('...') to add an object or array |
- Returns:
JSON. - NULL:
NULLwhendocorpathisNULL. ANULLvalue adds JSONnull. - Notes: exactly one path and one value. When the path is missing or is not an array, nothing changes (MySQL would make an array; KDB does not).
Example: Add a tag to a company
Setup:
UPDATE companies
SET profile = json_array_append(profile, '$.tags', 'dividend 2082/83')
WHERE symbol = 'CITY';
SELECT profile -> 'tags' AS tags FROM companies WHERE symbol = 'CITY';
Result:
| tags (JSON) |
|---|
| ["bank","A class","dividend 2082/83"] |
(1 row)
Example
SELECT json_array_append('[1, 2]', '$', 3) AS number,
json_array_append('[1, 2]', '$', json('[3, 4]')) AS inner_array,
json_array_append('{"tags": "bank"}', '$.tags', 'x') AS not_an_array;
Result:
| number (JSON) | inner_array (JSON) | not_an_array (JSON) |
|---|---|---|
| [1,2,3] | [1,2,[3,4]] | {"tags":"bank"} |
(1 row)
->, ->>
Syntax:
doc -> 'key' doc -> 0 doc -> -1
doc ->> 'key' doc ->> 0
doc -> 'a' -> 'b' ->> 0
Short ways to take one step into JSON. -> gives the part as JSON. ->> gives it as text: a string without quotes, other values as their JSON text.
| Right side | Meaning |
|---|---|
'key' | the key of an object |
0, 1, ... | an element of an array (0 is the first) |
-1, -2, ... | counted from the end (-1 is the last) |
- Returns:
->givesJSON;->>givesSTRING. - NULL:
NULLwhen either side isNULL, or the key or element is not there.->>on JSONnullgivesNULL. - Notes: each arrow takes one step.
doc -> 'office.city'looks for a key namedoffice.city, anddoc -> '$.office'looks for a key named$.office: both giveNULL. Chain arrows, or use a path function. - The left side can be a
JSONvalue or text that holds JSON (bad JSON is an error). - The result of
->>is text, sodoc ->> 'n' + 1is an error. UseCAST(doc ->> 'n' AS LONG), orjson_value(...). - Differences: the same as PostgreSQL for one step. MySQL's
->takes a path (doc -> '$.a'); KDB's does not. The PostgreSQL operators#>and#>>take a whole path at once and work too:profile #> '{office,city}'is json_extract_path and#>>is json_extract_path_text. The key tests?,?|,?&and the containment tests@>,<@are in the next section. - Speed:
doc ->> 'key' = 'text'(also<>,<,IN (...)) in aWHEREis read straight from the stored JSON of each row, without building the row first. A GIN index makes it faster still when few rows match.
Example: Company website, city and first tag
SELECT symbol,
profile ->> 'website' AS website,
profile -> 'office' ->> 'city' AS city,
CAST(profile -> 'tags' -> 0 AS STRING) AS first_tag_json,
profile -> 'tags' ->> -1 AS last_tag_text
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | website (STRING) | city (STRING) | first_tag_json (STRING) | last_tag_text (STRING) |
|---|---|---|---|---|
| CITY | https://www.citybank.example.com | Kathmandu | "bank" | A class |
| COAST | https://www.coastalbank.example.com | Kathmandu | "bank" | bank |
| VOLT | https://www.voltriver.example.com | NULL | NULL | NULL |
(3 rows)
Example: One step at a time
SELECT profile -> 'office' -> 'city' AS chained,
profile -> 'office.city' AS dotted_key,
profile -> '$.office' AS path_text
FROM companies
WHERE symbol = 'CITY';
Result:
| chained (JSON) | dotted_key (STRING) | path_text (STRING) |
|---|---|---|
| "Kathmandu" | NULL | NULL |
(1 row)
Example: Filter and group by a JSON key
SELECT profile -> 'office' ->> 'city' AS city, count(*) AS companies
FROM companies
GROUP BY profile -> 'office' ->> 'city'
ORDER BY city;
Result:
| city (STRING) | companies (LONG) |
|---|---|
| Kathmandu | 2 |
| NULL | 1 |
(2 rows)
Example: ->> gives text: cast it before you calculate (shows an error)
SELECT profile ->> 'branches' + 10 FROM companies WHERE symbol = 'CITY';
Result:
Error: line 1, column 31: '270' and '10' are not both numbers
@>, <@, ?, ?|, ?&
Syntax:
doc @> '{"key": value}' doc holds all of this
'{"key": value}' <@ doc the same, the other way round
doc ? 'key' doc has this top-level key
doc ?| ARRAY['a', 'b'] doc has at least one of these keys
doc ?& ARRAY['a', 'b'] doc has all of these keys
The PostgreSQL jsonb tests. Each gives TRUE or FALSE, so they go in a WHERE.
| Test | True when |
|---|---|
a @> b | every key of b is in a with a value that holds b's value, at every level. A list in b is held when each of its elements is in a's list, in any order. |
a <@ b | b @> a |
doc ? 'k' | an object with the key k at the top, a list with the string "k", or the string "k" |
doc ?| keys | doc ? k for at least one k of the array |
doc ?& keys | doc ? k for every k of the array (an empty array: always) |
- Numbers compare by value:
{"n": 1}holds{"n": 1.0}. A string never equals a number:{"n": 1}does not hold{"n": "1"}. - Lists:
[1, 2, [1, 3]] @> [1, 3]is false (the 3 is only inside the inner list);@> [[1, 3]]is true. At the top, a list holds a single value:'["a", "b"]' @> '"a"'. ?looks at the top level only:'{"a": {"b": 1}}' ? 'b'is false.- The keys of
?|and?&are a text array:ARRAY['a', 'b']or'{a,b}'. - NULL:
NULLwhen either side isNULL. - On two arrays,
@>and<@work as before:ARRAY[1, 2, 3] @> ARRAY[3, 1]. - Speed: a GIN index on the column finds the rows without reading every document.
Example: Banks with an A class tag, and companies with a capacity
SELECT symbol FROM companies WHERE profile @> '{"tags": ["A class"]}';
SELECT symbol FROM companies WHERE profile @> '{"office": {"city": "Kathmandu"}}' ORDER BY symbol;
SELECT symbol FROM companies WHERE profile ? 'capacity_mw';
Result:
SELECT symbol FROM companies WHERE profile @> '{"tags": ["A class"]}'
| symbol (STRING) |
|---|
| CITY |
(1 row)
SELECT symbol FROM companies WHERE profile @> '{"office": {"city": "Kathmandu"}}' ORDER BY symbol
| symbol (STRING) |
|---|
| CITY |
| COAST |
(2 rows)
SELECT symbol FROM companies WHERE profile ? 'capacity_mw'
| symbol (STRING) |
|---|
| VOLT |
(1 row)
Example: Any or all of some keys
SELECT symbol,
profile ?| ARRAY['rivers', 'branches'] AS has_one,
profile ?& ARRAY['office', 'branches'] AS has_both,
'{"branches": 270}' <@ profile AS has_270
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | has_one (BOOL) | has_both (BOOL) | has_270 (BOOL) |
|---|---|---|---|
| CITY | true | true | true |
| COAST | true | true | false |
| VOLT | true | false | false |
(3 rows)
SQL/JSON path: jsonb_path_query, @?, @@
Syntax:
jsonb_path_exists(document, 'path' [, vars [, silent]]) TRUE when the path finds anything
jsonb_path_match(document, 'path' [, vars [, silent]]) the TRUE / FALSE the path gives
jsonb_path_query(document, 'path' [, vars [, silent]]) one row per item the path finds
jsonb_path_query_array(document, 'path' [, vars [, silent]]) the items as one JSON list
jsonb_path_query_first(document, 'path' [, vars [, silent]]) the first item, or NULL
document @? 'path' jsonb_path_exists, never an error
document @@ 'path' jsonb_path_match, never an error
PostgreSQL's JSON path language: find parts of a document, keep the ones a filter likes, and do sums on them.
It is bigger than the simple '$.a.b' paths of json_extract.
| Path part | Meaning |
|---|---|
$ | the whole document |
.key, ."key with spaces", .* | a key; any key |
[0], [last], [last - 1], [1 to 3], [0, 2], [*] | elements of a list (0 is the first) |
.**, .**{2}, .**{1 to last} | every value below, at any level (or those levels) |
? (condition) | keeps the items the condition is true for; @ is the item |
==, !=, <>, <, <=, >, >= | compare (numbers by value, text by its characters) |
&&, ||, !( ) | and, or, not |
exists (path), (condition) is unknown | a path finds something; a comparison could not be made |
like_regex "pattern" flag "i", starts with "text" | text tests (flags i, s, q) |
+, -, *, /, % | sums on single numbers |
.type(), .size(), .double(), .abs(), .floor(), .ceiling(), .keyvalue() | methods |
$name | a variable from vars (a JSON object: '{"min": 100}') |
- Lax (the default): a key step on a list looks inside its elements, an element step on a single value treats it as a list of one, and a key that is not there finds nothing. Write
strict $...to make such mistakes errors. - Returns:
BOOLfor_exists,_match,@?and@@;JSONfor the others (PostgreSQL'sjsonb: Postgres clients get it asjsonb, written{"a": 1}). - NULL: a
NULLdocument, path or vars givesNULL(andjsonb_path_queryno rows).@@givesNULLwhen the answer is unknown. - Errors: with the SQLSTATE PostgreSQL uses, such as
2203A(strictand no such key),22038(a sum on more than one number) and42601(a mistake in the path).silent => true(the fourth argument) turns the "not found" and "wrong type" errors into no result, as@?and@@always do. - Speed: the path is read once per statement, not once per row.
doc @? '...',doc @@ '...'and the calls withsilenttrue run in the column engine on the stored text, like->>. - Differences:
.datetime()and thelike_regexflagsmandxare an error (PostgreSQL has noxeither).
Example: Companies with more than 300 branches, and every tag as a row
SELECT symbol FROM companies WHERE profile @? '$.branches ? (@ > 300)';
SELECT symbol, jsonb_path_query(profile, '$.tags[*]') AS tag FROM companies ORDER BY symbol;
Result:
SELECT symbol FROM companies WHERE profile @? '$.branches ? (@ > 300)'
| symbol (STRING) |
|---|
| COAST |
(1 row)
SELECT symbol, jsonb_path_query(profile, '$.tags[*]') AS tag FROM companies ORDER BY symbol
| symbol (STRING) | tag (JSON) |
|---|---|
| CITY | "bank" |
| CITY | "A class" |
| COAST | "bank" |
(3 rows)
Example: A condition as the whole path, and a value given as a variable
SELECT symbol,
profile @@ '$.branches > 300' AS many_branches,
jsonb_path_exists(profile, '$.branches ? (@ >= $min)', '{"min": 270}') AS at_least_270
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | many_branches (BOOL) | at_least_270 (BOOL) |
|---|---|---|
| CITY | false | true |
| COAST | true | true |
| VOLT | false | false |
(3 rows)
Example: Lists and sums
SELECT jsonb_path_query_array('{"eps": [12.1, 6.35, 3.2]}', '$.eps[*] ? (@ > 5)') AS big,
jsonb_path_query_first('{"eps": [12.1, 6.35, 3.2]}', '$.eps[last]') AS last_one,
jsonb_path_query_first('{"eps": [12.1, 6.35, 3.2]}', '$.eps.size()') AS how_many,
jsonb_path_query_first('{"eps": [12.1, 6.35, 3.2]}', '$.eps[0] / 2') AS half;
Result:
| big (JSON) | last_one (JSON) | how_many (JSON) | half (JSON) |
|---|---|---|---|
| [12.1,6.35] | 3.2 | 3 | 6.0500000000000000 |
(1 row)
Example: Every key and value of an object, and text tests
SELECT jsonb_path_query('{"npl_pct": 2.6, "car_pct": 13.4}', '$.keyvalue()') AS pair;
SELECT symbol FROM companies WHERE profile @? '$.website ? (@ like_regex "coastal|volt" flag "i")' ORDER BY symbol;
Result:
SELECT jsonb_path_query('{"npl_pct": 2.6, "car_pct": 13.4}', '$.keyvalue()') AS pair
| pair (JSON) |
|---|
| {"id":0,"key":"car_pct","value":13.4} |
| {"id":0,"key":"npl_pct","value":2.6} |
(2 rows)
SELECT symbol FROM companies WHERE profile @? '$.website ? (@ like_regex "coastal|volt" flag "i")' O
| symbol (STRING) |
|---|
| COAST |
| VOLT |
(2 rows)
Example: strict: a key that is not there is an error (shows an error)
SELECT jsonb_path_query('{"branches": 270}', 'strict $.office');
Result:
Error: line 1, column 8: jsonb_path_query: JSON object does not contain key "office"
Example: silent: the same mistake gives no result
SELECT count(*) FROM jsonb_path_query('{"branches": 270}', 'strict $.office', '{}', true);
Result:
| count(*) (LONG) |
|---|
| 0 |
(1 row)
GIN index on a JSON column
Syntax:
CREATE INDEX name ON table USING GIN (json_column)
An index of the keys and values inside every document of the column. It finds the rows for:
| Condition | Example |
|---|---|
@> a JSON value | extra @> '{"category": "auth"}' |
?, ?|, ?& | extra ? 'error' |
->> equal to text, or IN a list of texts | extra ->> 'category' = 'auth', extra -> 'user' ->> 'role' IN ('admin', 'owner') |
- The index finds the rows that may match; the condition is then checked on those rows. So the answer is always the same as without the index.
EXPLAINshowsuse index name (JSON keys and values ...)when it is used. It is not used for<>,<,>, or for@> '{}'(nothing to look up).- Like every KDB index it lives in memory and is built from the rows when first needed (see indexes). It costs roughly the size of the keys and values it holds.
USING GINon a column that is notJSONmakes an ordinary index, as before.- An index made with
USING GINbefore 2026-09-24 is an ordinary index on the whole value: drop it and create it again to get this kind.
Example: Find log rows by a key inside the JSON
Setup:
CREATE TABLE app_logs (id LONG PRIMARY KEY, message STRING, extra JSON);
INSERT INTO app_logs VALUES
(1, 'login ok', '{"category": "auth", "user": {"id": 7}}'),
(2, 'feed loaded', '{"category": "feed", "items": 20}'),
(3, 'login failed', '{"category": "auth", "user": {"id": 9}, "error": "bad password"}');
CREATE INDEX app_logs_extra ON app_logs USING GIN (extra);
SELECT id, message FROM app_logs WHERE extra ->> 'category' = 'auth' ORDER BY id;
SELECT id, message FROM app_logs WHERE extra @> '{"user": {"id": 9}}';
SELECT id, message FROM app_logs WHERE extra ? 'error';
Result:
SELECT id, message FROM app_logs WHERE extra ->> 'category' = 'auth' ORDER BY id
| id (LONG) | message (STRING) |
|---|---|
| 1 | login ok |
| 3 | login failed |
(2 rows)
SELECT id, message FROM app_logs WHERE extra @> '{"user": {"id": 9}}'
| id (LONG) | message (STRING) |
|---|---|
| 3 | login failed |
(1 row)
SELECT id, message FROM app_logs WHERE extra ? 'error'
| id (LONG) | message (STRING) |
|---|---|
| 3 | login failed |
(1 row)
json_build_object, jsonb_build_object
Syntax:
json_build_object(key, value [, key, value ...])
jsonb_build_object(key, value [, key, value ...])
Builds a JSON object from pairs: first a key, then its value, then the next pair. jsonb_build_object is the same function under the PostgreSQL jsonb name.
| Argument | Type | Meaning |
|---|---|---|
key | STRING (a number is turned into text) | the key name |
value | any | the value for that key; JSON stays JSON |
- Returns:
JSON(an object). With no arguments:{}. - NULL: a
NULLvalue becomes JSONnull. ANULLkey is an error. - Notes: an odd number of arguments is an error. json_object does the same thing with the same shape.
Example: A company card for the app
SELECT json_build_object('symbol', symbol, 'name', name, 'sector', sector,
'city', profile ->> 'office.city') AS card
FROM companies
WHERE symbol = 'CITY';
Result:
| card (JSON) |
|---|
| {"symbol":"CITY","name":"City Bank Limited","sector":"Banking","city":null} |
(1 row)
Example: Pairs must be complete (shows an error)
SELECT json_build_object('symbol');
Result:
Error: line 1, column 8: json_build_object needs pairs of key and value (an even number of arguments)
json_build_array, jsonb_build_array
Syntax:
json_build_array(value [, value ...])
jsonb_build_array(value [, value ...])
Builds a JSON list from the values, in order. jsonb_build_array is the same function under the PostgreSQL jsonb name.
| Argument | Type | Meaning |
|---|---|---|
value | any | one element of the list; JSON stays JSON |
- Returns:
JSON(a list). With no arguments:[]. - NULL: a
NULLvalue becomes JSONnull. The result is neverNULL. - Notes:
ARRAY[...]builds the same value and is shorter to write (see arrays).
Example: Quarter results as a small list
SELECT symbol, json_build_array(fiscal_year, quarter, eps) AS row_as_list
FROM fundamentals
WHERE symbol = 'CITY'
ORDER BY quarter;
Result:
| symbol (STRING) | row_as_list (JSON) |
|---|---|
| CITY | ["2082/83",3,11.40] |
| CITY | ["2082/83",4,12.10] |
(2 rows)
to_jsonb
Syntax:
to_jsonb(value)
Turns one SQL value into JSON: text becomes a JSON string with " around it, a number stays a number, TRUE/FALSE stay true/false, and a JSON value stays as it is.
| Argument | Type | Meaning |
|---|---|---|
value | any | the value to turn into JSON |
- Returns:
JSON. - NULL:
to_jsonb(NULL)isNULL(SQL NULL, not JSONnull). - Notes: the same as to_json; PostgreSQL apps write
to_jsonb. Use it to make the new value for jsonb_set, so text is not read as JSON by mistake.
Example
SELECT to_jsonb(symbol) AS as_json_string,
to_jsonb(eps) AS as_number,
to_jsonb(extra) AS json_stays_json
FROM fundamentals
WHERE symbol = 'VOLT';
Result:
| as_json_string (JSON) | as_number (JSON) | json_stays_json (JSON) |
|---|---|---|
| "VOLT" | 3.20 | {"energy_gwh":2105.4,"plant_factor_pct":52.7} |
(1 row)
row_to_json, to_json_row
Syntax:
row_to_json(value)
to_json_row(value)
Turns a value into JSON, the way a PostgreSQL app calls it on a row. KDB has no whole-row value, so give it a list or an object you built yourself.
| Argument | Type | Meaning |
|---|---|---|
value | any | the value to turn into JSON |
- Returns:
JSON. - NULL:
row_to_json(NULL)isNULL. - Notes: for a whole row, build it with
json_build_object('column', column, ...).SELECT row_to_json(t) FROM table t, the usual PostgreSQL way, does not work here.
Example: One row of the company list as JSON
SELECT row_to_json(json_build_object('symbol', symbol, 'name', name)) AS row_json
FROM companies
ORDER BY symbol;
Result:
| row_json (JSON) |
|---|
| {"symbol":"CITY","name":"City Bank Limited"} |
| {"symbol":"COAST","name":"Coastal Bank Limited"} |
| {"symbol":"VOLT","name":"Volt River Hydropower Limited"} |
(3 rows)
jsonb_set
Syntax:
jsonb_set(document, path_array, new_value)
jsonb_set(document, path_array, new_value, create_missing)
The document with one part set to new_value. The path is an array of steps ('{office,city}'), not a $. path.
| Argument | Type | Meaning |
|---|---|---|
document | JSON | the document to change |
path_array | array or array text | the steps down to the part, like '{office,city}' or ARRAY['tags', 0] |
new_value | JSON | the new value; use to_jsonb(x) for text or a number |
create_missing | BOOL | TRUE (default): add the key when it is not there. FALSE: only change a key that is already there. |
- Returns:
JSON. The saved row does not change until youUPDATEit. - NULL: a
NULLdocument givesNULL. ANULLpath gives the document back. ANULLnew value givesNULL(as PostgreSQL does). - Notes: a new value that is not JSON is an error -
'A'is not JSON,to_jsonb('A')is. json_set does the same with a'$.office.city'path.
Example: Mark a quarter as audited
Setup:
UPDATE fundamentals
SET extra = jsonb_set(extra, '{audited}', 'true')
WHERE symbol = 'COAST' AND quarter = 4;
SELECT symbol, quarter, extra FROM fundamentals WHERE symbol = 'COAST';
Result:
| symbol (STRING) | quarter (INT) | extra (JSON) |
|---|---|---|
| COAST | 4 | {"npl_pct":4.10,"car_pct":12.2,"audited":true} |
(1 row)
Example: create_missing = FALSE changes only keys that are there
SELECT jsonb_set(extra, '{npl_pct}', '3.00') AS changed,
jsonb_set(extra, '{rating}', to_jsonb('A'), FALSE) AS not_added,
jsonb_set(extra, '{rating}', to_jsonb('A')) AS added
FROM fundamentals
WHERE symbol = 'COAST' AND quarter = 4;
Result:
| changed (JSON) | not_added (JSON) | added (JSON) |
|---|---|---|
| {"npl_pct":3.00,"car_pct":12.2} | {"npl_pct":4.10,"car_pct":12.2} | {"npl_pct":4.10,"car_pct":12.2,"rating":"A"} |
(1 row)
Example: The new value must be JSON (shows an error)
SELECT jsonb_set(extra, '{rating}', 'A') FROM fundamentals WHERE symbol = 'COAST';
Result:
Error: line 1, column 8: jsonb_set: the new value is not JSON (use to_jsonb(...) for text)
jsonb_set_lax
Syntax:
jsonb_set_lax(document, path_array, new_value [, create_missing [, null_value_treatment]])
jsonb_set that says what a NULL new value means, instead of giving NULL.
null_value_treatment | A NULL new value ... |
|---|---|
'use_json_null' (default) | sets the part to JSON null |
'delete_key' | removes the part (as #-) |
'return_target' | leaves the document as it is |
'raise_exception' | is an error (SQLSTATE 22004) |
- Returns:
JSON(jsonb). A new value that is notNULLworks exactly as injsonb_set. - NULL: a
NULLdocument, path orcreate_missinggivesNULL. ANULLtreatment, or a word not in the table, is an error.
Example: Clear a key when the app sends no value
SELECT jsonb_set_lax('{"phone": "01-5970015", "city": "Kathmandu"}', '{phone}', NULL, true, 'delete_key') AS removed,
jsonb_set_lax('{"phone": "01-5970015", "city": "Kathmandu"}', '{phone}', NULL) AS json_null;
Result:
| removed (JSON) | json_null (JSON) |
|---|---|
| {"city":"Kathmandu"} | {"phone":null,"city":"Kathmandu"} |
(1 row)
jsonb_insert
Syntax:
jsonb_insert(document, path_array, new_value [, insert_after])
The document with new_value added at the path, never replacing anything (PostgreSQL).
- In a list: the last step is a place; the value goes before it (
insert_aftertrue: after it).-1is the last element. A place past either end adds at that end. - In an object: the last step is a new key. A key that is already there is an error (SQLSTATE
22023): use jsonb_set to change it. - Returns:
JSON(jsonb). When the path's parent is not there, the document comes back unchanged. - NULL: any
NULLargument givesNULL.
Example: Add a tag at the front, at the end, and a new key
SELECT jsonb_insert(profile, '{tags,0}', '"listed"') AS first_tag,
jsonb_insert(profile, '{tags,-1}', '"retail"', true) AS last_tag,
jsonb_insert(profile, '{office,fax}', '"01-5970016"') AS with_fax
FROM companies
WHERE symbol = 'CITY';
Result:
| first_tag (JSON) | last_tag (JSON) | with_fax (JSON) |
|---|---|---|
| {"website":"https://www.citybank.example.com","office":{"city":"Kathmandu","phone":"01-5970015"},"branches":270,"tags":["listed","bank","A class"]} | {"website":"https://www.citybank.example.com","office":{"city":"Kathmandu","phone":"01-5970015"},"branches":270,"tags":["bank","A class","retail"]} | {"website":"https://www.citybank.example.com","office":{"city":"Kathmandu","phone":"01-5970015","fax":"01-5970016"},"branches":270,"tags":["bank","A class"]} |
(1 row)
Example: An existing key is not replaced (shows an error)
SELECT jsonb_insert('{"branches": 270}', '{branches}', '300');
Result:
Error: line 1, column 8: jsonb_insert: cannot replace existing key (use jsonb_set to replace the value of a key)
jsonb_strip_nulls, json_strip_nulls
Syntax:
jsonb_strip_nulls(document)
json_strip_nulls(document)
The document without the keys whose value is JSON null, at every level. A null inside a list stays.
- Returns:
JSON(jsonb_strip_nulls:jsonb). NULL:NULLgivesNULL. - Notes: a key written twice counts once in
jsonb_strip_nulls, with its last value ({"a":1,"a":null}gives{});json_strip_nullskeeps both and removes only thenullone.
Example
SELECT jsonb_strip_nulls('{"eps": 12.1, "dividend": null, "notes": {"audit": null}, "q": [null, 1]}') AS stripped;
Result:
| stripped (JSON) |
|---|
| {"eps":12.1,"notes":{},"q":[null,1]} |
(1 row)
jsonb_pretty
Syntax:
jsonb_pretty(document)
The document as text on many lines, four spaces per level, exactly as PostgreSQL writes it (keys in jsonb order: shorter keys first).
- Returns:
STRING. NULL:NULLgivesNULL.
Example
SELECT jsonb_pretty(profile) FROM companies WHERE symbol = 'COAST';
Result:
| jsonb_pretty(profile) (STRING) |
|---|
| { "tags": [ "bank" ], "office": { "city": "Kathmandu" }, "website": "https://www.coastalbank.example.com", "branches": 380 } |
(1 row)
jsonb_object
Syntax:
jsonb_object('{key, value, key, value ...}')
jsonb_object('{{key, value}, {key, value} ...}')
jsonb_object(keys_array, values_array)
A JSON object from a text array of pairs, or from a list of keys and a list of values (PostgreSQL). Every value becomes a JSON string.
- Returns:
JSON(jsonb). A key given twice keeps its last value. - NULL: a
NULLvalue becomes JSONnull; aNULLkey is an error (22004). An odd number of elements, rows that are not pairs, or lists of different lengths are errors (2202E).
Example
SELECT jsonb_object('{city,Kathmandu,branches,270}') AS from_pairs,
jsonb_object(ARRAY['city', 'branches'], ARRAY['Pokhara', '12']) AS from_two_lists;
Result:
| from_pairs (JSON) | from_two_lists (JSON) |
|---|---|
| {"city":"Kathmandu","branches":"270"} | {"city":"Pokhara","branches":"12"} |
(1 row)
array_to_json
Syntax:
array_to_json(array [, pretty])
An array as a JSON list: ARRAY[1, 2] gives [1,2], ARRAY['a', 'b'] gives ["a","b"].
- Returns:
JSON. Withprettytrue: the text PostgreSQL gives, a line break after each comma of the outer list (asSTRING, since KDB keeps JSON values compact). - NULL:
NULLgivesNULL.
Example
SELECT array_to_json(ARRAY[513, 515.6, 527.1]) AS prices;
Result:
| prices (JSON) |
|---|
| [513,515.6,527.1] |
(1 row)
json_extract_path, jsonb_extract_path
Syntax:
json_extract_path(document, key [, key ...])
jsonb_extract_path(document, key [, key ...])
The part of the document at those steps, as JSON. Each step is one argument: a key of an object, or the number of a place in a list (counting from 0).
| Argument | Type | Meaning |
|---|---|---|
document | JSON | the document to read |
key | STRING | one step down: a key name, or a number for a list |
- Returns:
JSON. With no steps: the whole document. - NULL: a step that is not there gives
NULL. ANULLdocument givesNULL. - Notes: the same as the operator
document #> '{office,city}'. For plain text instead of JSON use json_extract_path_text. For a'$.office.city'path use json_extract.
Example: City and first tag of a company
SELECT json_extract_path(profile, 'office', 'city') AS city,
json_extract_path(profile, 'tags', '0') AS first_tag,
json_extract_path(profile, 'office', 'street') AS not_there
FROM companies
WHERE symbol = 'CITY';
Result:
| city (JSON) | first_tag (JSON) | not_there (STRING) |
|---|---|---|
| "Kathmandu" | "bank" | NULL |
(1 row)
json_extract_path_text, jsonb_extract_path_text
Syntax:
json_extract_path_text(document, key [, key ...])
jsonb_extract_path_text(document, key [, key ...])
The same as json_extract_path, but the answer is text: a JSON string comes back without its quotes.
| Argument | Type | Meaning |
|---|---|---|
document | JSON | the document to read |
key | STRING | one step down: a key name, or a number for a list |
- Returns:
STRING. - NULL: a step that is not there, or a JSON
null, givesNULL. - Notes: the same as the operator
document #>> '{office,city}'. Text cannot be used in sums: cast it first (::DOUBLE).
Example: Company office cities as plain text
SELECT symbol,
json_extract_path_text(profile, 'office', 'city') AS city,
json_extract_path_text(profile, 'website') AS website
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | city (STRING) | website (STRING) |
|---|---|---|
| CITY | Kathmandu | https://www.citybank.example.com |
| COAST | Kathmandu | https://www.coastalbank.example.com |
| VOLT | NULL | https://www.voltriver.example.com |
(3 rows)
json_typeof, jsonb_typeof
Syntax:
json_typeof(value)
jsonb_typeof(value)
What kind of JSON the value is: object, array, string, number, boolean or null.
| Argument | Type | Meaning |
|---|---|---|
value | JSON (or text that holds JSON) | the value to look at |
- Returns:
STRING. - NULL: a SQL
NULLgivesNULL. The JSON valuenullgives the textnull. - Notes: the same as json_type. Use it before you read a part whose shape is not the same in every row.
Example: Which profiles have a list of tags?
SELECT symbol,
json_typeof(profile) AS profile_is,
jsonb_typeof(profile -> 'tags') AS tags_is
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | profile_is (STRING) | tags_is (STRING) |
|---|---|---|
| CITY | object | array |
| COAST | object | array |
| VOLT | object | NULL |
(3 rows)
jsonb_array_length
Syntax:
jsonb_array_length(json_array)
How many elements a JSON list has.
| Argument | Type | Meaning |
|---|---|---|
json_array | JSON | a JSON list |
- Returns:
LONG. - NULL: a
NULLvalue givesNULL. - Notes: the PostgreSQL name for json_array_length; both give the same number. A value that is not a list gives
NULL, as in PostgreSQL.
Example: How many tags does a company have
SELECT symbol, jsonb_array_length(profile -> 'tags') AS tags
FROM companies
WHERE symbol = 'CITY';
Result:
| symbol (STRING) | tags (LONG) |
|---|---|
| CITY | 2 |
(1 row)
Example: The same number three ways
SELECT symbol,
json_array_length(profile -> 'tags') AS tags,
cardinality(profile -> 'tags') AS same_number
FROM companies
ORDER BY symbol;
Result:
| symbol (STRING) | tags (LONG) | same_number (LONG) |
|---|---|---|
| CITY | 2 | 2 |
| COAST | 1 | 1 |
| VOLT | NULL | NULL |
(3 rows)
-, #- (delete from a document)
Syntax:
document - 'key'
document - ARRAY['key1', 'key2']
document - position
document #- '{step,step,...}'
The document without some part of it (PostgreSQL). The saved row does not change until you UPDATE it.
| Form | What goes |
|---|---|
doc - 'key' | that key of an object; in a list, every string element equal to 'key' |
doc - ARRAY['a', 'b'] | each of those keys |
doc - 2 | the element at that place of a list, counting from 0; -1 is the last |
doc #- '{office,city}' | the part the path points to; a number step is a place in a list |
- Returns:
JSON. A key or place that is not there leaves the document as it is. - NULL: a
NULLdocument or argument givesNULL. - Notes: a number on an object (
'{"a":1}'::jsonb - 0) and anything on a plain value ('5'::jsonb - 'a') is an error, as in PostgreSQL. A path step that is not a number where the document has a list is an error too. json_remove does the same with'$.a'paths.
Example: Leave out keys, list elements and a nested part
SELECT profile - 'website' AS no_website,
profile - ARRAY['website', 'office'] AS two_keys_less,
profile -> 'tags' - 0 AS tags_without_first,
profile #- '{office,phone}' AS no_phone
FROM companies
WHERE symbol = 'CITY';
Result:
| no_website (JSON) | two_keys_less (JSON) | tags_without_first (JSON) | no_phone (JSON) |
|---|---|---|---|
| {"office":{"city":"Kathmandu","phone":"01-5970015"},"branches":270,"tags":["bank","A class"]} | {"branches":270,"tags":["bank","A class"]} | ["A class"] | {"website":"https://www.citybank.example.com","office":{"city":"Kathmandu"},"branches":270,"tags":["bank","A class"]} |
(1 row)
Example: A number on an object is an error (shows an error)
SELECT '{"a": 1}'::jsonb - 0;
Result:
Error: line 1, column 26: cannot delete from object using integer index
JSON to rows: jsonb_each, jsonb_array_elements, ...
Syntax:
SELECT * FROM jsonb_each(document) -- key, value (JSON)
SELECT * FROM jsonb_each_text(document) -- key, value (text)
SELECT * FROM jsonb_array_elements(list) -- value (JSON)
SELECT * FROM jsonb_array_elements_text(list) -- value (text)
SELECT * FROM jsonb_object_keys(document) -- jsonb_object_keys
These functions make rows from one JSON value, as in PostgreSQL: one row per key of an object, or per element of a list. The json_ names (json_each, json_array_elements, ...) are the same functions.
| Function | Rows | Columns |
|---|---|---|
jsonb_each(doc) | one per key | key (text), value (JSON) |
jsonb_each_text(doc) | one per key | key, value (text: a string without quotes, JSON null as NULL) |
jsonb_array_elements(list) | one per element | value (JSON) |
jsonb_array_elements_text(list) | one per element | value (text) |
jsonb_object_keys(doc) | one per key | jsonb_object_keys (text) |
- In FROM: the argument is a value: a constant, a
$1parameter, or a subquery like(SELECT profile FROM companies WHERE symbol = 'CITY'). A column of another table in the sameFROM(PostgreSQL'sFROM t, jsonb_each(t.doc)) is not supported yet. - In the SELECT list:
jsonb_array_elements,jsonb_array_elements_textandjsonb_object_keysalso work, one row per element for each row, like unnest. That is the way to split a column of every row. - NULL: a
NULLvalue gives no rows. - Notes: a value of the wrong shape is an error, as in PostgreSQL:
jsonb_eachof a list,jsonb_array_elementsof an object.AS e(k, v)renames the columns.
Example: Every key and value of a profile
SELECT key, value
FROM jsonb_each_text((SELECT profile FROM companies WHERE symbol = 'COAST'));
Result:
| key (STRING) | value (STRING) |
|---|---|
| website | https://www.coastalbank.example.com |
| office | {"city":"Kathmandu"} |
| branches | 380 |
| tags | ["bank"] |
(4 rows)
Example: One row per tag of every company (SELECT list)
SELECT symbol, jsonb_array_elements_text(profile -> 'tags') AS tag
FROM companies
WHERE profile ? 'tags'
ORDER BY symbol, tag;
Result:
| symbol (STRING) | tag (STRING) |
|---|---|
| CITY | A class |
| CITY | bank |
| COAST | bank |
(3 rows)
Example: The keys of a document
SELECT * FROM jsonb_object_keys('{"npl_pct": 2.6, "car_pct": 13.4}');
Result:
| jsonb_object_keys (STRING) |
|---|
| npl_pct |
| car_pct |
(2 rows)
Example: Not an object (shows an error)
SELECT * FROM jsonb_each('[1, 2]');
Result:
Error: line 1, column 15: jsonb_each: cannot deconstruct an array as an object
json_to_record, json_to_recordset
Syntax:
SELECT * FROM json_to_record(document) AS r(column TYPE, ...)
SELECT * FROM json_to_recordset(list_of_objects) AS r(column TYPE, ...)
A JSON object as one row, or a list of objects as rows, with the columns and types you write after the alias (PostgreSQL). An app can send many rows as one JSON parameter and insert them with one statement. jsonb_to_record and jsonb_to_recordset are the same.
| Argument | Type | Meaning |
|---|---|---|
document / list_of_objects | JSON or text | {"id": 1, ...} or [{"id": 1, ...}, {"id": 2, ...}] |
AS r(column TYPE, ...) | each column and its type; a column is read from the key with its name |
- Returns: rows with those columns. A key that is missing, or JSON
null, givesNULL. Keys without a column are left out. - Types: each value is converted as
CAST(value AS TYPE):"2.5"and2.5both give aDOUBLE; aJSONcolumn keeps the part as JSON; aTEXTcolumn gets a string without its quotes, anything else as JSON text. A value that does not fit the type is an error that names the column and the row. - NULL: a
NULLvalue gives no rows.
Example: Rows from a JSON list, as an app would send them
SELECT *
FROM json_to_recordset('[{"symbol": "CITY", "eps": 12.1, "audited": true},
{"symbol": "COAST", "eps": "6.35"}]') AS r(symbol TEXT, eps DOUBLE, audited BOOL);
Result:
| symbol (STRING) | eps (DOUBLE) | audited (BOOL) |
|---|---|---|
| CITY | 12.1 | true |
| COAST | 6.35 | NULL |
(2 rows)
Example: Each column needs a type (shows an error)
SELECT * FROM json_to_record('{"id": 1}') AS r(id);
Result:
Error: line 1, column 15: json_to_record: column 'id' needs a type, as in AS r(id INT, sym TEXT)
jsonb_populate_record, jsonb_populate_recordset
Syntax:
SELECT * FROM jsonb_populate_record(NULL::table_name, document)
SELECT * FROM jsonb_populate_recordset(NULL::table_name, list_of_objects)
Like json_to_record, but the columns and their types are those of a table (PostgreSQL's row type). json_populate_record(set) is the same.
- Returns: rows with the table's columns, each value converted as an
INSERTinto that column would do it. Missing keys giveNULL. - Notes: the first argument must be written
NULL::table_name; KDB does not take a row of values there. It is only forFROM(andINSERT INTO t SELECT * FROM ...).
Example: Insert rows sent as JSON into a table
Setup:
INSERT INTO fundamentals (id, symbol, fiscal_year, quarter, eps)
SELECT id, symbol, fiscal_year, quarter, eps
FROM jsonb_populate_recordset(NULL::fundamentals,
'[{"id": 90, "symbol": "VOLT", "fiscal_year": "2083/84", "quarter": 1, "eps": "1.10"}]');
SELECT id, symbol, quarter, eps FROM fundamentals WHERE id = 90;
Result:
| id (LONG) | symbol (STRING) | quarter (INT) | eps (DECIMAL(2)) |
|---|---|---|---|
| 90 | VOLT | 1 | 1.10 |
(1 row)