KDB knowledge

JSON functions

Check, read, build and change JSON values, like company profiles and report numbers.

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.

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)
CITYKathmanduhttps://www.citybank.example.com270NULL
COASTKathmanduhttps://www.coastalbank.example.com380NULL
VOLTNULLhttps://www.voltriver.example.comNULLVolt 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.

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"}falseobject
2nullfalsenull
3NULLtrueNULL
45falsenumber

(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)
2City Bank profit rises 18%120
3Volt River output up10
1Market index gains 12 points9

(3 rows)

SELECT id, title, json_value_long(stats, '$.views') AS views FROM news ORDER BY views DESC

id (LONG)title (STRING)views (LONG)
2City Bank profit rises 18%120
3Volt River output up10
1Market index gains 12 points9

(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.

PartMeaning
$the whole document
.namethe 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

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.1trueNULL

(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.

ArgumentTypeMeaning
textSTRING (or JSON)JSON text, like '{"a": 1}'

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.

ArgumentTypeMeaning
valueany typethe value to turn into JSON

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.52.50true"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.

ArgumentTypeMeaning
textSTRING (or any value)the text to check

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)
truefalsefalsefalseNULL

(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)
2COAST{"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.

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGa JSON path like '$.office.city'

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"]
VOLTNULLNULLNULLNULL

(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 valueSQL result
"text"STRING without quotes
a whole number like 270LONG
a number with a point or e, like 2.60DOUBLE (2.6)
true, falseBOOL
null, an object, an arrayNULL
ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGa JSON path

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)
Kathmandu270NULLNULL

(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)
COASTCoastal Bank Limited4.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.

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGa JSON path

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)
CITY313.12.752.75
CITY413.42.602.6
COAST412.24.104.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").

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGa JSON path

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.5512.5NULLNULL

(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)
VOLT52.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.

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGa JSON path

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)
270270270NULLNULL

(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".

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGa JSON path

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)
truefalseNULLNULL

(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)
CITY2082/834

(1 row)

json_type

Syntax:

json_type(doc)
json_type(doc, path)

The kind of JSON value: object, array, string, number, boolean or null.

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGoptional JSON path; default '$' (the whole document)

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)
objectobjectarraynumberstringNULL

(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)
CITY3NULL
CITY4boolean
COAST4NULL
VOLT4NULL

(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.

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGoptional JSON path to the array; default '$'

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)
CITY2NULL
COAST1NULL
VOLTNULL1

(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)
30NULL

(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.

ArgumentTypeMeaning
docJSON or JSON textthe document
pathSTRINGoptional JSON path to the object; default '$'

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)
CITY3["npl_pct","car_pct","deposits"]
CITY4["npl_pct","car_pct","deposits","audited"]
COAST4["npl_pct","car_pct"]
VOLT4["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.

ArgumentTypeMeaning
keySTRINGthe name of the key (a number is turned into text)
valueany typethe value; NULL becomes JSON null

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.

ArgumentTypeMeaning
valueany typean element; NULL becomes JSON null

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.

ArgumentTypeMeaning
docJSON or JSON textthe document to change
pathSTRINGa JSON path
valueany typethe new value (text becomes a JSON string; use json('...') for JSON)

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.

ArgumentTypeMeaning
docJSON or JSON textthe document to change
pathSTRINGa JSON path
valueany typethe value to add

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.

ArgumentTypeMeaning
docJSON or JSON textthe document to change
pathSTRINGa JSON path
valueany typethe new value

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"}
VOLTNULL

(3 rows)

json_remove

Syntax:

json_remove(doc, path [, path ...])

Removes the keys or array elements at the paths.

ArgumentTypeMeaning
docJSON or JSON textthe document to change
pathSTRINGa JSON path; up to 63 paths

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)
CITY3{"npl_pct":2.75,"car_pct":13.1}
CITY4{"npl_pct":2.60,"car_pct":13.4,"audited":false}
COAST4{"npl_pct":4.10,"car_pct":12.2}
VOLT4{"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.

ArgumentTypeMeaning
docJSON or JSON textthe document to change
pathSTRINGa JSON path to an array ('$' when doc itself is the array)
valueany typethe new element; use json('...') to add an object or array

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 sideMeaning
'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)

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)
CITYhttps://www.citybank.example.comKathmandu"bank"A class
COASThttps://www.coastalbank.example.comKathmandu"bank"bank
VOLThttps://www.voltriver.example.comNULLNULLNULL

(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"NULLNULL

(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)
Kathmandu2
NULL1

(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.

TestTrue when
a @> bevery 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 <@ bb @> a
doc ? 'k'an object with the key k at the top, a list with the string "k", or the string "k"
doc ?| keysdoc ? k for at least one k of the array
doc ?& keysdoc ? k for every k of the array (an empty array: always)

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)
CITYtruetruetrue
COASTtruetruefalse
VOLTtruefalsefalse

(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 partMeaning
$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 unknowna 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
$namea variable from vars (a JSON object: '{"min": 100}')

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)
CITYfalsetrue
COASTtruetrue
VOLTfalsefalse

(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.236.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:

ConditionExample
@> a JSON valueextra @> '{"category": "auth"}'
?, ?|, ?&extra ? 'error'
->> equal to text, or IN a list of textsextra ->> 'category' = 'auth', extra -> 'user' ->> 'role' IN ('admin', 'owner')

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)
1login ok
3login failed

(2 rows)

SELECT id, message FROM app_logs WHERE extra @> '{"user": {"id": 9}}'

id (LONG)message (STRING)
3login failed

(1 row)

SELECT id, message FROM app_logs WHERE extra ? 'error'

id (LONG)message (STRING)
3login 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.

ArgumentTypeMeaning
keySTRING (a number is turned into text)the key name
valueanythe value for that key; JSON stays JSON

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.

ArgumentTypeMeaning
valueanyone element of the list; JSON stays JSON

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.

ArgumentTypeMeaning
valueanythe value to turn into JSON

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.

ArgumentTypeMeaning
valueanythe value to turn into JSON

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.

ArgumentTypeMeaning
documentJSONthe document to change
path_arrayarray or array textthe steps down to the part, like '{office,city}' or ARRAY['tags', 0]
new_valueJSONthe new value; use to_jsonb(x) for text or a number
create_missingBOOLTRUE (default): add the key when it is not there. FALSE: only change a key that is already there.

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)
COAST4{"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_treatmentA 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)

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).

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.

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).

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.

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"].

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).

ArgumentTypeMeaning
documentJSONthe document to read
keySTRINGone step down: a key name, or a number for a list

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.

ArgumentTypeMeaning
documentJSONthe document to read
keySTRINGone step down: a key name, or a number for a list

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)
CITYKathmanduhttps://www.citybank.example.com
COASTKathmanduhttps://www.coastalbank.example.com
VOLTNULLhttps://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.

ArgumentTypeMeaning
valueJSON (or text that holds JSON)the value to look at

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)
CITYobjectarray
COASTobjectarray
VOLTobjectNULL

(3 rows)

jsonb_array_length

Syntax:

jsonb_array_length(json_array)

How many elements a JSON list has.

ArgumentTypeMeaning
json_arrayJSONa JSON list

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)
CITY2

(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)
CITY22
COAST11
VOLTNULLNULL

(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.

FormWhat 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 - 2the 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

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.

FunctionRowsColumns
jsonb_each(doc)one per keykey (text), value (JSON)
jsonb_each_text(doc)one per keykey, value (text: a string without quotes, JSON null as NULL)
jsonb_array_elements(list)one per elementvalue (JSON)
jsonb_array_elements_text(list)one per elementvalue (text)
jsonb_object_keys(doc)one per keyjsonb_object_keys (text)

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)
websitehttps://www.coastalbank.example.com
office{"city":"Kathmandu"}
branches380
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)
CITYA class
CITYbank
COASTbank

(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.

ArgumentTypeMeaning
document / list_of_objectsJSON 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

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)
CITY12.1true
COAST6.35NULL

(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.

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))
90VOLT11.10

(1 row)