This is the support code for a blog post about using JSON in PostgreSQL.
Read the full article here.
This is the support code for a blog post about using JSON in PostgreSQL.
Read the full article here.
CREATE TABLE people ( | |
first_name text, | |
last_name text, | |
characteristics jsonb, | |
id uuid default gen_random_uuid(), | |
PRIMARY KEY (id) | |
); | |
INSERT INTO people VALUES | |
('George', 'Castanza', '{"favorites":{"holidays":["Summer of George"]}}'), | |
('Cosmo', 'Kramer', '{"inventions": ["Coffee Table Book", "The Bro"]}'); | |
SELECT ('{"favorites":{"holidays":["Summer of George"]}}'::jsonb)->'favorites'->'holidays' AS holidays; | |
-- returns a row with a jsonb column with the contents: '["Summer of George"]' |
SELECT (:FAVES::jsonb)->'favorites'->'holidays' AS holidays; | |
-- assumes you provide :FAVES as a value in the request template, and it contains stringified JSON |
BEGIN; | |
SET CONSTRAINTS ALL DEFERRED; | |
INSERT INTO people | |
SELECT * | |
FROM json_populate_recordset (NULL::people, ' | |
[ | |
{ | |
"id": "9eda3717-ee4a-4ac7-ba2e-d3b43b7e79c4", | |
"last_name": "Castanza", | |
"first_name": "George", | |
"characteristics": { | |
"favorites": { | |
"holidays": [ | |
"Summer of George" | |
] | |
}, | |
"enemies": [ | |
{ | |
"id": "5d7994d2-7960-4d73-9e1d-b4c255703765", | |
"flavor": "Unexplained" | |
}, | |
{ | |
"id": "a4adc3fd-485f-4fc7-a561-863ea78e9bb9", | |
"flavor": "Teacher" | |
} | |
] | |
} | |
}, | |
{ | |
"id": "6ae1da27-0565-4649-96c5-e4c1da48de21", | |
"last_name": "Kramer", | |
"first_name": "Cosmo", | |
"characteristics": { | |
"inventions": [ | |
"Coffee Table Book", | |
"The Bro" | |
] | |
} | |
}, | |
{ | |
"id": "a4adc3fd-485f-4fc7-a561-863ea78e9bb9", | |
"last_name": "Mr.", | |
"first_name": "Heyman", | |
"characteristics": { | |
"occupation": "Gym Teacher (Fired)" | |
} | |
}, | |
{ | |
"id": "5d7994d2-7960-4d73-9e1d-b4c255703765", | |
"last_name": "Joe", | |
"first_name": "Davola", | |
"characteristics": { | |
"enemies":[ | |
{ | |
"id": "9eda3717-ee4a-4ac7-ba2e-d3b43b7e79c4", | |
"flavor": "Occupational Rivalry" | |
} | |
] | |
} | |
} | |
]'); | |
COMMIT; |
import psycopg2 | |
from psycopg2 import sql | |
from psycopg2.extras import Json as pgJson | |
conn = psycopg2.connect(""" ... """) | |
cur = conn.cursor() | |
# table_name is a string of the table name to import into | |
# json_data is an list of dicts (array of objects, in JSON-speak) of the data to import | |
try: | |
cur.execute(sql.SQL("insert into {0} SELECT * FROM json_populate_recordset (NULL::{0}, %s)").format( | |
sql.Identifier(table_name)), [pgJson(json_data)]) | |
conn.commit() | |
except: | |
conn.rollback() | |
# Handle the error |
SELECT | |
-- typical list of columns to return | |
p.first_name, enemy.id, enemy.first_name, enemy.characteristics | |
FROM | |
people p, | |
-- get the enemies info from the `characteristics` column, | |
-- treating it like a table on it's own | |
-- with jsonb_array_elements | |
jsonb_array_elements(p.characteristics->'enemies') enemy_info | |
-- typical left join, also searching the "people" table | |
-- note: getting the 'id' value as string then casting to uuid | |
LEFT JOIN people enemy ON (enemy_info->>'id')::uuid = enemy.id | |
WHERE | |
p.id = ?; -- assuming ? is provided by the application, properly escaped | |
-- output is a list of rows with the requested columns, | |
-- including one jsonb column 'enemy.characteristics' |
SELECT row_to_json(people.*) FROM people; | |
-- One row for each person, each with a single json column like this: |
{ | |
"id": 0, | |
"first_name": "George", | |
"last_name": "Castanza", | |
"characteristics": { | |
"favorites": { | |
"holidays": ["Summer of George"] | |
} | |
} | |
} |
SELECT row_to_json(named.*) | |
FROM | |
( | |
SELECT | |
people.*, | |
concat_ws(' ', first_name, last_name) AS full_name | |
FROM people | |
) AS named; | |
-- Returns the same as the prvious query, but with a new full_name key |
SELECT jsonb_build_object('x', 'xylophone', 'y', 'yak'); | |
-- Returns: { "x": "xylophone", "y": "yak" } |
SELECT jsonb_build_object( | |
'x', 'xylophone', | |
'name', jsonb_build_object('f', first_name, 'l', last_name) | |
) FROM people; | |
-- Returns rows like: { "x": "xylophone", "name": { "f": "George", "l": "Castanza" } } |
SELECT people.characteristics || jsonb_build_object( | |
'full_name', | |
concat_ws(' ', first_name, last_name) | |
) | |
FROM people; | |
-- Returns one row for each person, each with a single json column like this: |
{ | |
"favorites": { "holidays": ["Summer of George"] }, | |
"full_name": "George Castanza" | |
} |
SELECT jsonb_agg( | |
jsonb_build_object( | |
'id', people.id, | |
'full_name', concat_ws(' ', first_name, last_name) | |
) | |
) | |
FROM people; | |
/* | |
Returns: | |
*/ | |
[ | |
{ | |
"id": "9eda3717-ee4a-4ac7-ba2e-d3b43b7e79c4", | |
"full_name": "George Castanza" | |
}, | |
{ | |
"id": "6ae1da27-0565-4649-96c5-e4c1da48de21", | |
"full_name": "Cosmo Kramer" | |
} | |
] |
SELECT jsonb_object_agg( | |
id, | |
jsonb_build_object( | |
'full_name', concat_ws(' ', first_name, last_name) | |
) | |
) | |
FROM people; | |
/* | |
Returns: | |
*/ |
{ | |
"6ae1da27-0565-4649-96c5-e4c1da48de21": { | |
"full_name": "Cosmo Kramer" | |
}, | |
"9eda3717-ee4a-4ac7-ba2e-d3b43b7e79c4": { | |
"full_name": "George Castanza" | |
} | |
} |
CREATE TABLE change_log ( | |
id uuid default gen_random_uuid(), | |
old_value jsonb, | |
new_value jsonb, | |
operation text, | |
table_name text, | |
created_timestamp timestamp not null default now(), | |
PRIMARY KEY (id) | |
); |
CREATE OR REPLACE FUNCTION change_log_fn() RETURNS TRIGGER AS $$ | |
DECLARE | |
new_log_uuid uuid; | |
BEGIN | |
IF (TG_OP = 'DELETE') THEN | |
INSERT INTO change_log (id, old_value, operation, table_name) | |
VALUES ( | |
DEFAULT, | |
row_to_json(OLD), | |
TG_OP, | |
TG_TABLE_NAME | |
) returning id into new_log_uuid; | |
RETURN OLD; | |
ELSIF (TG_OP = 'INSERT') THEN | |
INSERT INTO change_log (id, new_value, operation, table_name) | |
VALUES ( | |
DEFAULT, | |
row_to_json(NEW), | |
TG_OP, | |
TG_TABLE_NAME | |
) returning id into new_log_uuid; | |
RETURN NEW; | |
ELSIF (TG_OP = 'UPDATE' and (OLD != NEW)) THEN | |
INSERT INTO change_log (id, old_value, new_value, operation, table_name) | |
VALUES ( | |
DEFAULT, | |
row_to_json(OLD), | |
row_to_json(NEW), | |
TG_OP, | |
TG_TABLE_NAME | |
) returning id into new_log_uuid; | |
RETURN NEW; | |
END IF; | |
RETURN NEW; | |
END; | |
$$ LANGUAGE plpgsql; |
DROP TRIGGER IF EXISTS people_change_tg ON people; | |
CREATE TRIGGER people_change_tg | |
AFTER INSERT OR UPDATE OR DELETE | |
ON people | |
FOR EACH ROW EXECUTE PROCEDURE change_log_fn(); |
UPDATE people | |
SET characteristics = ( | |
characteristics || jsonb_build_object( | |
'inventions', characteristics->'inventions' || '["The Beach"]'::jsonb | |
) | |
) | |
WHERE id = '6ae1da27-0565-4649-96c5-e4c1da48de21'; |
{ | |
"id": "6ae1da27-0565-4649-96c5-e4c1da48de21", | |
"last_name": "Kramer", | |
"first_name": "Cosmo", | |
"characteristics": { | |
"inventions": [ | |
"Coffee Table Book", | |
"The Bro" | |
] | |
} | |
} |
{ | |
"id": "6ae1da27-0565-4649-96c5-e4c1da48de21", | |
"last_name": "Kramer", | |
"first_name": "Cosmo", | |
"characteristics": { | |
"inventions": [ | |
"Coffee Table Book", | |
"The Bro", | |
"The Beach" | |
] | |
} | |
} |