4

I created a simple database (in latest stable postgresql), like this:

create table table_a(id int primary key not null, name char(10));
create table table_b(id int primary key not null, name char(10), parent_a_id int);
create table table_c(id int primary key not null, name char(10), parent_a_id int, parent_b_id int, parent_c_id int, c_number int);
create table table_d(id int primary key not null, name char(10), parent_c_id int, d_number int);

with some example data like this:

insert into table_a(id, name) values(1, "a");

insert into table_b(id, name, parent_a_id) values(1, "b", 1);

insert into table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(1, "c1", 1, 1, null, 1);
insert into table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(2, "c1.1", 1, 1, 1, 5);
insert into table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(3, "c1.1.1", 1, 1, 2, 2);
insert into table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(4, "c1.2", 1, 1, 1, 8);
insert into table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(5, "c2", 1, 1, null, 4);

insert into table_d(id, name, parent_c_id, d_number) values(1, "c1_d1", 1, 5);
insert into table_d(id, name, parent_c_id, d_number) values(2, "c1.1_d1", 2, 6);
insert into table_d(id, name, parent_c_id, d_number) values(3, "c1.1_d2", 2, 1);
insert into table_d(id, name, parent_c_id, d_number) values(4, "c1.1.1_d1", 3, 2);
insert into table_d(id, name, parent_c_id, d_number) values(5, "c2_d1", 5, 4);
insert into table_d(id, name, parent_c_id, d_number) values(6, "c2_d2", 5, 3);
insert into table_d(id, name, parent_c_id, d_number) values(7, "c2_d3", 5, 7);

Now I want to generate json like this: http://codebeautify.org/jsonviewer/cb9bc2a1

With relation rules:

  1. table_a has many table_b
  2. table_b has one table_a and has many table_c (select only where table_c_id is null)
  3. table_c has one table_a and has one table_b and has many table_c (children) and has one table_c (parent)

and couting rules:

  1. table_c has d_numbers_sum (sum of d_number in table_d and sum of d_numbers_sum in table_c relation )
  2. table_b has d_numbers_sum (sum of d_numbers_sum in table_c relation )
  3. table_a has d_numbers_sum (sum of d_numbers_sum in table_b relation )
  4. table_c has real_c_number (if has children_c then sum of real_c_number in table_c relation else c_number)
  5. table_b has real_c_number_sum (sum of real_c_number in table_c relation )
  6. table_a has real_c_number_sum (sum of real_c_number_sum in table_b relation )

Is it possible to generate that JSON with that rules in pure postgresql code?

Is it possible to generate shourtcat function for this like:

select * from my_shourtcat where id = ?;

or whitout id (generate json array):

select * from my_shourtcat;

Can you show me an example with description (how to generate nested json and couting), so I could use relations similar, but more complex that these in my app?

EDIT:

I wrote something interesting, but it's not 100% nested hash - here all leaf has own tree and result is an array of these trees I need to deep merge that array to create array of unique trees:

with recursive j as (
    SELECT c.*, json '[]' children -- at max level, there are only leaves
    FROM test.table_c c
    WHERE (select count(1) from test.table_c where parent_c_id = c.id) = 0
  UNION ALL
    -- a little hack, because PostgreSQL doesn't like aggregated recursive terms
    SELECT (c).*, array_to_json(array_agg(j)) children
    FROM (
      SELECT c, j
      FROM j
      JOIN test.table_c c ON j.parent_c_id = c.id
    ) v
    GROUP BY v.c
)
SELECT json_agg(row_to_json(j)) json_tree FROM j WHERE parent_c_id is null;
Eiji
  • 340
  • 5
  • 14

1 Answers1

4

The answer consists of two parts. First to rig up a basic json structure, and then to build up nested json objects from self-referencing column in table_c.

UPDATE: I rewrote example/part 2 as a pure sql solution, and added that code as example 3. I also added a plsql function that encapsulates almost all code, that takes the name of a view as input to produce the nested json. See example 4.

All code requires Postgres 9.5.

The first code sets up a json object with most joins except for the nested children in table_c. The counting part is mostly left out.

In the second code example I wrote a "merge" function in pure plpgsql, which should solve the nested json problem. This solution requires only PG9.5 and no extensions, since plpgsql is built in.

As an alternative, I found one other solution that requires plv8 installed which does a deep merge in javascript ).

Creating nested json is not trivial to do in pure sql, where the challenge is to merge the separate json trees we can get from a recursive CTE.

Code example 1

Creating the query as a view makes it easy to reuse the query to either return a json array of all objects from table_a, or return only one object with a given id.

I made some small changes to the data model and data. The code for a self-contained example follows:

--TABLES
DROP SCHEMA IF EXISTS TEST CASCADE;
CREATE SCHEMA test;

-- Using text instead of char(10), to avoid padding. For most     databases text is the best choice. 
-- Postgresql uses the same implementation the hood (char vs text)
-- Source: https://www.depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text/

create table test.table_a(id int primary key not null, name text);
create table test.table_b(id int primary key not null, name text, parent_a_id int);
create table test.table_c(id int primary key not null, name text, parent_a_id int, parent_b_id int, parent_c_id int, c_number int);
create table test.table_d(id int primary key not null, name text, parent_c_id int, d_number int);

--DATA
insert into test.table_a(id, name) values(1, 'a');

-- Changed: parent_a_id=1 (instead of null)
insert into test.table_b(id, name, parent_a_id) values(1, 'b', 1);

insert into test.table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(1, 'c1', 1, 1, null, 1);
insert into test.table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(2, 'c1.1', 1, 1, 1, 5);
insert into test.table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(3, 'c1.1.1', 1, 1, 2, 2);
insert into test.table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(4, 'c1.2', 1, 1, 1, 8);
insert into test.table_c(id, name, parent_a_id, parent_b_id, parent_c_id, c_number) values(5, 'c2', 1, 1, null, 4);

insert into test.table_d(id, name, parent_c_id, d_number) values(1, 'c1_d1', 1, 5);
insert into test.table_d(id, name, parent_c_id, d_number) values(2, 'c1.1_d1', 2, 6);
insert into test.table_d(id, name, parent_c_id, d_number) values(3, 'c1.1_d2', 2, 1);
insert into test.table_d(id, name, parent_c_id, d_number) values(4, 'c1.1.1_d1', 3, 2);
insert into test.table_d(id, name, parent_c_id, d_number) values(5, 'c2_d1', 5, 4);
insert into test.table_d(id, name, parent_c_id, d_number) values(6,'c2_d2', 5, 3);
insert into test.table_d(id, name, parent_c_id, d_number) values(7, 'c2_d3', 5, 7);


CREATE OR REPLACE VIEW json_objects AS
--Root object
SELECT ta.id, json_build_object(
    'id', ta.id,
    'name', ta.name,
    'd_numbers_sum', (SELECT sum(d_number) FROM test.table_d),
    'real_c_number_sum', null,
    'children_b', (

        -- table_b
        SELECT json_agg(json_build_object(
            'id', tb.id,
            'name', tb.name,
            'd_numbers_sum', null,
            'real_c_number_sum', null,
            'children_c', (

                -- table_c
                SELECT json_agg(json_build_object(
                   'id', tc.id,
                   'name', tc.name,
                   'd_numbers_sum', null,
                   'real_c_number_sum', null,
                   'children_d', (

                        -- table_d
                        SELECT json_agg(json_build_object(
                           'id', td.id,
                           'name', td.name,
                           'd_numbers_sum', null,
                           'real_c_number_sum', null
                        ))
                        FROM test.table_d td
                        WHERE td.parent_c_id = tc.id
                    )
                ))
                FROM test.table_c tc
                WHERE tc.parent_b_id = tb.id
            )
        ))
        FROM test.table_b tb
        WHERE tb.parent_a_id = ta.id
    )
) AS object
FROM test.table_a ta


-- Return json array of all objects
SELECT json_agg(object) FROM json_objects;

-- Return only json object with given id
SELECT object FROM json_objects WHERE id = 1

Code example 2

Here we map the data from table_c so we can insert it directly into a recursive CTE from the documentation, for readability and educational purposes. Then prepares the data as input to the "merge" function. For simplicity I just aggregated the rows into a big json object. The performance should be ok. We can choose to get the parent object, or only its children as an (json)array in the third function parameter.

Which node to get the children for is specified in the last query in the last lines of the example. This query can be used all places where we need the children for a table_c node. I did test this on a more complex example and it looks like I sorted out most rough edges.

The three parts of the CTE (graph, search_graph and filtered_graph) can be refactored into one for performance, since CTE's are optimization fences for the database planner, but I kept this version for readability and debugging.

This example utilizes jsonb instead of json, see the documentation. The reason for using jsonb here is not having to reparse the json each time we manipulate it in the function. When the function is done, the result is casted back to json so it can be inserted directly into the code in example 1.

--DROP VIEW test.tree_path_list_v  CASCADE;
CREATE OR REPLACE VIEW test.tree_path_list_v AS 
WITH RECURSIVE
    -- Map the source data so we can use it directly in a recursive query from the documentation:
    graph AS
    (
        SELECT id AS id, parent_c_id AS link, name, jsonb_build_object('id', id, 'name', name, 'parent_c_id', parent_c_id, 'parent_a_id', parent_a_id, 'parent_b_id', parent_b_id) AS data
        FROM test.table_c
    ),
    -- Recursive query from documentation.
    -- http://www.postgresql.org/docs/current/static/queries-with.html
    search_graph(id, link, data, depth, path, cycle) AS (
        SELECT g.id, g.link, g.data, 1,
          ARRAY[g.id],
          false
        FROM graph g
      UNION ALL
        SELECT g.id, g.link, g.data, sg.depth + 1,
          path || g.id,
          g.id = ANY(path)
        FROM graph g, search_graph sg
        WHERE g.id = sg.link AND NOT cycle
    ),
    -- Decorate/filter the result so it can be used as input to the "test.create_jsonb_tree" function
    filtered_graph AS (
        SELECT
            sg.path[1] AS id,
            sg.path[2] AS parent_id,
            sg.depth AS level,
            sg.id AS start_id,
            d.name,
            sg.path,
            d.data::jsonb AS json
        FROM search_graph sg
        INNER JOIN graph d ON d.id = sg.path[1]
        ORDER BY level DESC
    )
    -- "Main" query
    SELECT * FROM filtered_graph
;


-- Returns a json object with all children merged into its parents.
-- Parameter 1 "_tree_path_list": A json document with rows from the view "test.tree_path_list_v" aggregates as one big json.
-- Parameter 2 "_children_keyname": Choose the name for the children
CREATE OR REPLACE FUNCTION test.create_jsonb_tree(_tree_path_list jsonb, _children_keyname text DEFAULT 'children', _get_only_children boolean DEFAULT false)
    RETURNS jsonb AS
$$
DECLARE
    node_map jsonb :=  jsonb_build_object();
    node_result jsonb := jsonb_build_array();
    parent_children jsonb := jsonb_build_array();
    node jsonb;
    relation jsonb;
BEGIN
    FOR node IN SELECT * FROM jsonb_array_elements(_tree_path_list)
    LOOP
        RAISE NOTICE 'Input (per row): %', node;
        node_map := jsonb_set(node_map, ARRAY[node->>'id'], node->'json');
    END LOOP;

    FOR relation IN SELECT * FROM jsonb_array_elements(_tree_path_list)
    LOOP
        IF ( (relation->>'level')::int > 1 ) THEN
            parent_children := COALESCE(node_map->(relation->>'parent_id')->_children_keyname, jsonb_build_array()) || jsonb_build_array(node_map->(relation->>'id'));
            node_map := jsonb_set(node_map, ARRAY[relation->>'parent_id', _children_keyname], parent_children);
            node_map := node_map - (relation->>'id');
        ELSE
            IF _get_only_children THEN
                node_result := node_map->(relation->>'id')->_children_keyname;
            ELSE
                node_result := node_map->(relation->>'id');
            END IF;
        END IF;
    END LOOP;
    RETURN node_result;
END;
$$ LANGUAGE plpgsql
;


-- Aggregate the rows from the view into a big json object. The function
SELECT test.create_jsonb_tree(
    (   SELECT jsonb_agg( (SELECT x FROM (SELECT id, parent_id, level, name, json) x) )
        FROM test.tree_path_list_v
        WHERE start_id = 1  --Which node to get children for
    ),
    'children'::text,
    true
)::json
;

Output for example 2

[
 {
    "id": 2,
    "name": "c1.1",
    "children": [
      {
        "id": 3,
        "name": "c1.1.1",
        "parent_a_id": 1,
        "parent_b_id": 1,
        "parent_c_id": 2
      }
    ],
    "parent_a_id": 1,
    "parent_b_id": 1,
    "parent_c_id": 1
  },
  {
    "id": 4,
    "name": "c1.2",
    "parent_a_id": 1,
    "parent_b_id": 1,
    "parent_c_id": 1
  }
]

Code example 3: pure sql nested json solution

I rewrote the nested-json code to pure sql, and put it into an SQL function so we can reuse the code by parameterizing the start_ids (as an array)

I have not benchmarked the code yet, and it does not necessarily perform better than the sql+plpgsql solution. I had to (ab)use CTEs to loop through the result the same way I do in plgsql to add nodes to their parents. The solution for "merging" is essentialy procedural even though it is pure sql.

--DROP VIEW test.source_data_v  CASCADE;
--Map your data (in this view) so it can be directly used in the recursive CTE.
CREATE OR REPLACE VIEW test.source_data_v AS
    SELECT 
        id AS id,
        parent_c_id AS parent_id,
        name as name, -- Only for debugging: Give the node a name for easier debugging (a name is easier than an id)
        --jsonb_build_object('id', tree_id, 'name', name, 'pid', parent_tree_id, 'children', jsonb_build_array()) AS data --Allow empty children arrays
        jsonb_build_object('id', id, 'name', name, 'parent_id', parent_c_id) AS data -- Ignore empty children arrays
    FROM test.table_c
;
SELECT * FROM test.source_data_v;


--DROP VIEW test.tree_path_list_v  CASCADE;
CREATE OR REPLACE FUNCTION test.get_nested_object(bigint[]) 
    RETURNS jsonb
AS $$
  WITH RECURSIVE
    search_graph(id, parent_id, data, depth, path, cycle) AS (
        SELECT g.id, g.parent_id, g.data, 1,
          ARRAY[g.id],
          false
        FROM test.source_data_v g
      UNION ALL
        SELECT g.id, g.parent_id, g.data, sg.depth + 1,
          path || g.id,
          g.id = ANY(path)
        FROM test.source_data_v g, search_graph sg
        WHERE g.id = sg.parent_id AND NOT cycle
    ),
    transformed_result_graph AS (
        SELECT
            sg.path[1] AS id,
            d.parent_id,
            sg.depth AS level,
            sg.id AS start_id,
            d.name,
            sg.path,
            (SELECT string_agg(t.name, ' ') FROM (SELECT unnest(sg.path::int[]) AS id) a INNER JOIN test.source_data_v t USING (id)) AS named_path,
            d.data
        FROM search_graph sg
        INNER JOIN test.source_data_v d ON d.id = sg.path[1]
        WHERE sg.id = ANY($1) --Parameterized input for start nodes
        ORDER BY level DESC, start_id ASC
    ),
    -- Sort path list and build a map/index of all individual nodes which we loop through in the next CTE:
    sorted_paths AS (
        SELECT null::int AS rownum, * 
        FROM transformed_result_graph WHERE false
        UNION ALL
        SELECT
            0, null, null, null, null, null, null, null,
            (SELECT jsonb_object_agg(id::text, data) FROM transformed_result_graph)  -- Build a map/index of all individual nodes
        UNION ALL
        SELECT row_number() OVER () as rownum, *
        FROM transformed_result_graph c
        ORDER BY level DESC, start_id ASC
    ),
    build_tree_loop (rownum, level, id, parent_id, data, named_path, result) AS (
        SELECT
            rownum, level, id, parent_id, data,
            named_path,
            data -- First row has the complete  node map
        FROM sorted_paths
        WHERE rownum = 0
        UNION ALL
        SELECT
            c.rownum, c.level, c.id, c.parent_id, c.data,
            c.named_path,
            CASE WHEN (c.parent_id IS NULL) OR (prev.result->(c.parent_id::text) IS NULL)
                 THEN prev.result
                 WHEN c.parent_id IS NOT NULL
                 THEN jsonb_set(
                        prev.result - (c.id::text),  -- remove node and add it as child
                        ARRAY[c.parent_id::text, 'children'], 
                        COALESCE(prev.result->(c.parent_id::text)->'children',jsonb_build_array())||COALESCE(prev.result->(c.id::text), jsonb_build_object('msg','ERROR')),  -- add node as child (and create empty children array if not exist)
                        true --add key (children) if not exists
                    )
            END AS result
        FROM sorted_paths c  -- Join each row in "sorted_paths" with the previous row from the CTE.
        INNER JOIN build_tree_loop prev ON c.rownum = prev.rownum+1
    ), nested_start_nodes AS (
        SELECT jsonb_agg(q.value) AS result
        FROM jsonb_each((SELECT result FROM build_tree_loop ORDER BY rownum DESC LIMIT 1)) q
    )
    -- "Main" query
    SELECT result FROM nested_start_nodes
$$ LANGUAGE sql STABLE;
-- END of sql function 

SELECT test.get_nested_object(ARRAY[1]);

Output: Unfortunately, jsonb does not preserver order, so "children" key comes first, making it harder to read the tree.

[
{
    "children": [
        {
            "children": [
                {
                    "id": 3,
                    "name": "c1.1.1",
                    "parent_id": 2
                }
            ],
            "id": 2,
            "name": "c1.1",
            "parent_id": 1
        },
        {
            "id": 4,
            "name": "c1.2",
            "parent_id": 1
        }
    ],
    "id": 1,
    "name": "c1",
    "parent_id": null
}
]

Code example 4

Another variant: I put everything into a plsql function. The dynamic query inside the function takes the name of any view/table as parameter, which contains columns id+parent_id+data+name. It also takes an array of ids for where to start. When using the function in a query you can aggregate a set of ids to an array as input. (array_agg etc).

The function is not "transparent", so it is harder to optimize indexes and such. With the "_debug" parameter set to true the function wil loutput the raw generated sql as a notice, so you can explain analyze the query.

/*
Parameters:
    _ids                Array of ids. Specify where to start recursion down the tree.
    _view               Name of a view/table with the source data. The view must contain the following colums:
                            id(int/bigint)
                            parent_id(int/bigint)
                            data(jsonb)  The data for each node, without the children key, which is added in this func.
                            name(text)   Name is optional, only used for debugging purposes, can be empty string.
    _children_keyname   What key to use for children arrays
    _no_root            Exclude the root node, only returning the children array. Makes less sense when returning multiple root nodes (dont know which children belongs to which roots)
*/          
--DROP FUNCTION test.get_nested_jsonb(bigint[], regclass, text, boolean, boolean) CASCADE;
CREATE OR REPLACE FUNCTION test.get_nested_jsonb(_ids bigint[], _view regclass, _children_keyname text DEFAULT 'children', _no_root boolean DEFAULT false, _debug boolean DEFAULT false)
    RETURNS jsonb AS $$
DECLARE
    dynamic_sql text := '';
    tree_path_list jsonb;
    node_map jsonb :=  jsonb_build_object();
    node_result jsonb := jsonb_build_array();
    parent_children jsonb := jsonb_build_array();
    node jsonb;
    relation jsonb;    
BEGIN
    dynamic_sql := format(
    '    
        WITH RECURSIVE
        search_graph(id, parent_id, depth, path, cycle) AS (
            SELECT g.id, g.parent_id, 1,
              ARRAY[g.id],
              false
            FROM '|| _view ||' g
          UNION ALL
            SELECT g.id, g.parent_id, sg.depth + 1,
              path || g.id,
              g.id = ANY(path)
            FROM '|| _view ||' g, search_graph sg
            WHERE g.id = sg.parent_id AND NOT cycle
        ),
        graph_by_id AS (
            SELECT
                sg.path[1] AS id, d.parent_id, sg.depth, sg.id AS start_id, d.name, sg.path,
                --(SELECT string_agg(t.name, '' '') FROM (SELECT unnest(sg.path::int[]) AS id) a INNER JOIN '|| _view ||' t USING (id)) AS named_path, -- For debugging, show the path as list of names instead of ids
                d.data
            FROM search_graph sg
            INNER JOIN '|| _view ||' d ON d.id = sg.path[1] -- Join in data for the current node
            WHERE sg.id = ANY($1) --Parameterized input for start nodes: To debug raw sql: replace variable $1 with array of ids: ARRAY[1]
            ORDER BY depth DESC, start_id ASC
        )
        SELECT jsonb_agg( (SELECT x FROM (SELECT id, parent_id, depth, name, data) x) )
        FROM graph_by_id
    ');
    IF _debug THEN
        RAISE NOTICE 'Dump of raw dynamic SQL. Remember to replace $1 with ARRAY[id1,id2]: %', dynamic_sql;
    END IF;
    EXECUTE dynamic_sql USING _ids INTO tree_path_list;

    -- Create a node map (id as key)
    FOR node IN SELECT * FROM jsonb_array_elements(tree_path_list)
    LOOP
        node := jsonb_set(node, ARRAY['data', _children_keyname], jsonb_build_array()); --add children key to all nodes
        node_map := jsonb_set(node_map, ARRAY[node->>'id'], node->'data');
    END LOOP;
    RAISE NOTICE 'dump: %', node_map;

    -- Loop sorted list, add nodes to node map from leaves and up
    FOR relation IN SELECT * FROM jsonb_array_elements(tree_path_list)
    LOOP
        IF ( (relation->>'depth')::int > 1 ) THEN
            parent_children := COALESCE(node_map->(relation->>'parent_id')->_children_keyname, jsonb_build_array()) || jsonb_build_array(node_map->(relation->>'id'));
            node_map := jsonb_set(node_map, ARRAY[relation->>'parent_id', _children_keyname], parent_children);
            node_map := node_map - (relation->>'id');
        ELSE
            IF _no_root THEN
                node_result := node_map->(relation->>'id')->_children_keyname;
            ELSE
                node_result := node_map->(relation->>'id');
            END IF;
        END IF;
    END LOOP;
    RETURN node_result;    
END;
$$ LANGUAGE plpgsql STABLE;

-- Test the function on a view 'test.source_data_v', starting from id=1
SELECT test.get_nested_jsonb(ARRAY[1], 'test.source_data_v', 'children', false, true);
Community
  • 1
  • 1
Geir Bostad
  • 886
  • 7
  • 18
  • Ah, I missed the self-referencing column in table_c. It should be doable to get a tree by extending the query with a recursive loop (recursive CTE in Postgres). Since table_c can have both a, b and c parents it might be a good idea to reuse the recursive part with a view or sql-function. I'll try to find some time to solve that part. – Geir Bostad May 19 '16 at 20:37
  • 1
    +1 for note about sql views, but it's not exactly what I want ... counting part is not problem, the main problem for this answer is nested table_c, you do not create code, just write how can I do it, I have a question for your code: if I want to count inside this object I should insert "as" (for example: "as children_d" before: "from test.table_d td where td.parent_c_id = tc.id"), right? – Eiji May 19 '16 at 20:37
  • ah, I already tried "with recursive", but I can't do it - It's not problem to me to create list, but I don't know how to create json tree within with recursive – Eiji May 19 '16 at 21:03
  • I am not sure I understand the question about count inside the object. You mean counting the number of children? As for the recursive part, looks like Postgres don't like aggregating (json) and recursiveness, and there are some hacks around it by [starting from the leaves](http://stackoverflow.com/questions/25678509/postgres-recursive-query-with-row-to-json) instead of working the way down. I am not sure if this is also an issue in 9.5, or if there are other and less complex workarounds, or if it really is an issue at all;) – Geir Bostad May 19 '16 at 21:19
  • With your actual code, I mean something like this: "select sum(d_number) from children_d" (in your table_c part - d_numbers_sum) with insert "as children_d" before: "from test.table_d td where td.parent_c_id = tc.id" (in your table_d part) - is it ok? – Eiji May 19 '16 at 21:33
  • I see it before. It's not an postgres issue. He is count max leaf level and if one leaf ends with depth 2 and second with depth 3 then first will not be collected - he collect all leafs but WITH MAX LEVEL - here is the issue. – Eiji May 19 '16 at 22:05
  • Look at my updated question, please. I wrote something really interesting. Can we finish it from my code? – Eiji May 19 '16 at 22:31
  • 1
    The solutions I find creates one json tree per path, requiring merging, as you suggest in your updated question. I tried doing it in pure sql using a window function to look at previous row, but didnt work AFAICT . Maybe write a specific aggregate function, but that may not be trivial nor elegant. Looks like we have to resort to a plpgsql function (or plv8) which can loop over the json trees and "merge" them. WIth PG9.5 we have jsonb_set to manipulate json easy. There is no jsonb_insert (to array) yet, which is planned for PG9.6, but can use the concat operator "||" with jsonb_set. – Geir Bostad May 23 '16 at 08:04
  • This solution seems to [handle siblings and duplicated roots](http://stackoverflow.com/questions/27438704/adjacency-list-to-json-graph-with-postgres) in the tree as well, but it requires plv8. I hoped there was pure sql/plpgsql-solution by now, after PG9.5 :/ I need this myself soon, so I am gonna keep on hacking and searching and will let you know if i come up with better solutions. – Geir Bostad May 23 '16 at 09:53
  • Are plpgsql and/or plv8 slower than pure-postgresql queries (with same query features)? I need to install a package from my distro repo or it's already included into one distribution-package (I'm using Funtoo Linux - Gentoo based)? If on serwers (huge number of database queries) it's not big diffrence or pure-postgresql query is slowest by problamatic implementation (like you wroted) I can accept this as a final answer (in second case - as a final answer for postgresql 9.5 - please note postgresql version in that answer). – Eiji May 23 '16 at 17:58
  • 1
    Hope you find my updated answer useful, with a pure plpgsql merge function. The performance looks OK so I will most likely put something like this into production myself, until someone solves this in pure sql (if that is even possible;) – Geir Bostad May 25 '16 at 18:07
  • Looks like really nice, but how to get all (as array)? If I comment line: "WHERE start_id = 1" I got empty result :-( – Eiji May 26 '16 at 09:01
  • You need "start_id" to to tell where to start nesting, it is the id to a row in table_c. The last argument in my function (test.create_jsonb_tree) is "_children_keyname" which decides whether the start node is in the result or not. If set to "true", as in the example, you get an jsonb array of children to the start node. This array can be inserted directly into the json you build up. – Geir Bostad May 26 '16 at 09:41
  • Ah, now I think I understand what you wanted. This code can only start from one node, you cannot get multiple start nodes in the result. This is intentional as I had to make some restrictions. Note that I modifed the code a bit with adding a cast from jsonb to json in the last line of my code in example2, and added some info about jsonb/json in the text. [This link shows what I edited.](http://stackoverflow.com/posts/37329992/revisions) Remember to scroll the code to see the diff – Geir Bostad May 26 '16 at 09:52
  • Sorry! I wrote the wrong parameter two comments ago. The parameter to choose only the children is "_get_only_children", which I set to true in the code example – Geir Bostad May 26 '16 at 10:06
  • 1
    @Eiji: I added a pure sql function if you're interested. – Geir Bostad Jun 03 '16 at 11:34
  • O.o You are excelent! Is it possible in sql to create multiple views (for 2+ tables with that relation - with same field name: id and parent_id) and pass view name to your function? So we will got a big function to build jsonb and simple view for any table. That code would be perfect. – Eiji Jun 07 '16 at 09:26
  • I am working on that=) I have tried plenty of ways to hide the complexity, make it dynamic, debuggable and most important, let the sql join part be as transparent as possible so it is easy to analyze it. Its almost impossible to achieve all those goals. But for your usecase it is probably enough to create a plpgsql function taking the view name and start-nodes as parameters and run a dynamic query and a foreach loop over the data(from my plpgsql solution). Then it is enough to create a view for each table and pass that name to the function which joins and serializes the data to json – Geir Bostad Jun 07 '16 at 10:00
  • I think you can do it easy: return two columns: id and json_data, so I can select (json_agg) data (your json) (and optionally: where id = or in ... - without json_agg) from your_function. And I can then query in my bigger view: "select json_agg(results.json_data) as final_result from test.get_nested_object(my_view_name) results" or: "select result.json_data as final_result from test.get_nested_object(my_view_name) result where id = 1" – Eiji Jun 07 '16 at 10:36
  • Can you do something more? In your output nodes with id 3 and 4 (leafs) does not have children, so I need to check if prop exists and then each. – Eiji Jun 07 '16 at 10:42
  • Ah, add children key with empty array? You can do that your self. Just add a "children" key with the value *jsonb_build_array()** in the view where you map your data. I put an an example in the code, its commented out. Search for "create empty children array". – Geir Bostad Jun 07 '16 at 12:10
  • Whoups, its a bug. Test.tree should be the view, swap it with ' || _view || ' like the other places in the dynamic sql – Geir Bostad Jun 07 '16 at 21:35
  • ok, now I get "tree_id" column error on right table for clausule using – Eiji Jun 07 '16 at 21:53
  • Fixed the bug. The named_path is not needed anyhow, its only for debugging. – Geir Bostad Jun 08 '16 at 08:38