I have a view that essentially presents a denormalized view onto my data.
It's roughly like having a blog table and a comments table, and the view then shows a blog post per row, with all the comments belonging to that post in an array in a json field.
The view uses the following code to achieve that:
(SELECT array_to_json(array_agg(row_to_json(x.*))) AS array_to_json FROM
(SELECT foo, bar
FROM table_b
WHERE a.id = table_b.abc_id) x) AS baz
I've made the view updatable, which works for everything except this kind of column. I also have some typical many-to-many relationships in that data, and those are automatically updated when you edit them via the view. But I couldn't figure out how to do the same for this pseudo-json column.
Essentially, I need to pass this json to an update/insert statement in my INSTEAD OF trigger. I experimented with json_to_recordset
, but I couldn't get that to do what I need.
Any idea how I could achieve this?
Best Answer
What you're trying to achieve is something quite uncommon. However, it is feasible.
Assumptions:
You have a certain parent/child table relationship. I call the two tables
parent
andchild
, and assume the following structure:NOTE: The primary key of the
child
table is NOT an arbitrarychild_id
field, but a (let's call it) natural key(parent_id, child_nr)
, that reads like '1st child of parent', '2nd child of parent', ... , 'nth child of parent'.For the sake of completeness, I fill the tables with some values:
At this point, we create a
VIEW
which shows all the information from the parent table, and also that of the children, in an aggregate form: a JSON column containing an array of JSON objects:At this point, this is the data shown by our view:
(The way that rows with no children are shown can be improved. This is first concept.)
We can now define a trigger function to handle the updates of this view:
and link the trigger function to an INSTEAD trigger:
When you perform an
UPDATE
of the view:This is what you get after:
The two little tricks, that basically follow the idea of the OP, with some small variations:
Use
json_agg(row(child.some_more_payload) ORDER BY child.child_nr)
to aggregate all children into a JSON array (of JSON objects), keeping order. It is necessary to have this data type to allow the next function to give back the rows.At the trigger, get back to a recordset by using
json_to_recordset(new.more_payload) AS x(f1 text)
, and INSERT this recordset into thechild
table.NOTE: I've not checked the 'null' cases, but it just a matter of adding some extra conditions to the trigger, and/or having some strategic COALESCE and/or NULLIF in the appropriate places.