How to select all records from many different tables in Postgres, and return a nested tree of data?
I have 9 tables which are for the most part similar (same 4 columns), but have the remaining columns differ between tables:
CREATE TABLE my_namespace_attachment (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
bucket_id integer,
value_hash character varying(255)
);
CREATE TABLE my_namespace_boolean (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
value boolean
);
CREATE TABLE my_namespace_decimal (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
value numeric
);
CREATE TABLE my_namespace_integer (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
value integer
);
CREATE TABLE my_namespace_object_binding (
object_organization_id bigint NOT NULL,
object_type_id integer NOT NULL,
object_id bigint NOT NULL,
object_property_id integer NOT NULL,
value_organization_id bigint NOT NULL,
value_type_id integer NOT NULL,
value_object_id bigint NOT NULL
);
CREATE TABLE my_namespace_property_binding (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
value_organization_id bigint,
value_type_id integer,
value_object_id integer,
value_property_id integer
);
CREATE TABLE my_namespace_string (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
value character varying(255)
);
CREATE TABLE my_namespace_text (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
value text
);
CREATE TABLE my_namespace_timestamp (
object_organization_id bigint,
object_type_id integer,
object_id bigint,
object_property_id integer,
value timestamp without time zone
);
What is the standard approach to select all records matching specific constraints, from all 9 tables, and include the "extra" properties for each table? Second part of the question is how to return it in a nested fashion, if possible (described below).
I basically would like the query to work like this:
SELECT * FROM
my_namespace_attachment,
my_namespace_boolean,
my_namespace_decimal,
my_namespace_integer,
my_namespace_object_binding,
my_namespace_property_binding,
my_namespace_string,
my_namespace_text,
my_namespace_timestamp
WHERE object_organization_id = 1
AND object_type_id = 2
AND object_id = 3
AND object_property_id = 4
I would like to use these in JavaScript as JSON records. I am using knex.js for making the queries from Node.js, if that matters.
Ideally, I would get the records back from SQL grouped in a nested fashion like this:
{
[organization_id]: {
[object_type_id]: {
[object_id]: {
[object_property_id]: {
...remainingProperties
}
}
}
}
}
So for the object_binding
table, it would look like:
{
[organization_id]: {
[object_type_id]: {
[object_id]: {
[object_property_id]: {
value_organization_id: something,
value_type_id: something2,
value_object_id: something3,
}
}
}
}
}
Or with specific data to demonstrate:
{
17: {
20: {
102: {
193: {
value_organization_id: 14,
value_type_id: 12,
value_object_id: 100,
}
}
}
}
}
Is anything like this possible? Ideally I would have be able to get the last nested structure directly from SQL (if it is efficient). If it is not efficient or not possible in SQL, I can just get back the arrays of records and reduce it to this structure at the application layer in JavaScript. But then the main question still is, how can I query all 9 tables and return all their each unique columns of data? Right now I am just making the 9 queries and reducing in JavaScript. Is that the best approach? Even if it is, I would still like to see how it is done querying all 9 at once.
Here's an attempt at creating something similar.
No view.
Just a lot of left joins on a sub-query with the criteria.
SELECT json_build_object ( obj.object_organization_id, json_build_object ( obj.object_type_id, json_build_object ( obj.object_id, json_build_object ( 'props', json_agg(json_build_object ( 'property_id', obj.object_property_id , 'value_organization_id', obj.value_organization_id , 'value_type_id', obj.value_type_id , 'value_object_id', obj.value_object_id , 'value_property_id', obj.value_property_id , 'attachment_bucket_id', attach.bucket_id , 'attachment_hash', attach.value_hash , 'boolean', bool.value , 'decimal', dec.value , 'integer', inte.value , 'string', string.value , 'text', txt.value , 'timestamp', ts.value ) -- property ) -- agg1 ) -- object ) -- type ) -- org ) as json FROM ( SELECT DISTINCT obj.object_organization_id , obj.object_type_id , obj.object_id , prop.object_property_id , prop.value_organization_id , prop.value_type_id , prop.value_object_id , prop.value_property_id FROM my_namespace_object_binding obj INNER JOIN my_namespace_property_binding prop USING (object_organization_id, object_type_id, object_id, object_property_id) WHERE obj.object_organization_id = 1 AND obj.object_type_id = 2 AND obj.object_id = 3 AND prop.object_property_id = 4 ) AS obj LEFT JOIN my_namespace_attachment AS attach USING (object_organization_id, object_type_id, object_id, object_property_id) LEFT JOIN my_namespace_boolean AS bool USING (object_organization_id, object_type_id, object_id, object_property_id) LEFT JOIN my_namespace_decimal AS dec USING (object_organization_id, object_type_id, object_id, object_property_id) LEFT JOIN my_namespace_integer AS inte USING (object_organization_id, object_type_id, object_id, object_property_id) LEFT JOIN my_namespace_string AS string USING (object_organization_id, object_type_id, object_id, object_property_id) LEFT JOIN my_namespace_text AS txt USING (object_organization_id, object_type_id, object_id, object_property_id) LEFT JOIN my_namespace_timestamp AS ts USING (object_organization_id, object_type_id, object_id, object_property_id) GROUP BY obj.object_organization_id , obj.object_type_id , obj.object_id;
| json | | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | {"1" : {"2" : {"3" : {"props" : [{"property_id" : 4, "value_organization_id" : null, "value_type_id" : null, "value_object_id" : null, "value_property_id" : null, "attachment_bucket_id" : null, "attachment_hash" : null, "boolean" : null, "decimal" : null, "integer" : null, "string" : null, "text" : null, "timestamp" : null}]}}}} |
db<>fiddle here
First question : how to query 9 tables ?
You can create a view, see the manual :
CREATE OR REPLACE VIEW my_view
( object_organization_id
, object_type_id
, object_id
, object_property_id
, bucket_id
, value_hash
, boolean_value
, decimal_value
, integer_value
, string_value
, text_value
, timestamp_value
, value_organization_id
, value_type_id
, value_object_id
, value_property_id
)
AS
SELECT a.object_organization_id
, a.object_type_id
, a.object_id
, a.object_property_id
, a.bucket_id
, a.value_hash
, b.boolean_value
, d.decimal_value
, i.integer_value
, s.string_value
, t.text_value
, ts.timestamp_value
, o.value_organization_id
, o.value_type_id
, o.value_object_id
, p.value_property_id
FROM my_namespace_attachment AS a
INNER JOIN my_namespace_boolean AS b USING (object_organization_id, object_type_id, object_id, object_property_id)
INNER JOIN my_namespace_decimal AS d USING (object_organization_id, object_type_id, object_id, object_property_id)
INNER JOIN my_namespace_integer AS i USING (object_organization_id, object_type_id, object_id, object_property_id)
INNER JOIN my_namespace_object_binding AS o USING (object_organization_id, object_type_id, object_id, object_property_id)
INNER JOIN my_namespace_property_binding AS p USING (object_organization_id, object_type_id, object_id, object_property_id, value_organization_id, value_type_id, value_object_id)
INNER JOIN my_namespace_string AS s USING (object_organization_id, object_type_id, object_id, object_property_id)
INNER JOIN my_namespace_text AS t USING (object_organization_id, object_type_id, object_id, object_property_id)
INNER JOIN my_namespace_timestamp AS ts USING (object_organization_id, object_type_id, object_id, object_property_id) ;
Then you can query the view directly :
SELECT *
FROM my_view
WHERE object_organization_id = 1
AND object_type_id = 2
AND object_id = 3
AND object_property_id = 4 ;
Second question : how to aggreate columns into a unique json data ?
In your example, double quotes are missing so that it can't be a json data generated from postgres. An output from postgres could be :
{
"17": {
"20": {
"102": {
"193": {
"value_organization_id": 14,
"value_type_id": 12,
"value_object_id": 100,
}
}
}
}
}
The query to get this kind of result may be like this :
SELECT jsonb_build_object( object_organization_id :: text ,
jsonb_build_object( object_type_id :: text ,
jsonb_build_object( object_id :: text ,
jsonb_build_object( object_property_id :: text ,
to_jsonb(row(value_organization_id, value_type_id, value_object_id))))))
FROM my_namespace_property_binding