0

I want to create a function to be used to get the node traversal path.

CREATE TYPE IDType AS (id uuid);

drop function F_ItemPath;
CREATE OR REPLACE FUNCTION F_ItemPath (item record)
RETURNS TABLE (item_id uuid, depth numeric)
AS $$
BEGIN
return QUERY
    WITH recursive item_path AS (
        SELECT ic.parent_item_id, depth=1
        from item_combination ic, item i 
        WHERE ic.child_item_id=i.id
        UNION all
        SELECT ic.parent_item_id, depth=ip.depth + 1
        FROM item_path ip, item_combination ic WHERE ip.parent_item_id=ic.child_item_id
        )
        SELECT item_id=ip.parent_item_id, depth=ip.depth FROM item_path ip;
END; $$
LANGUAGE plpgsql;

select * from F_ItemPath(('55D6F516-7D8F-4DF3-A4E5-1E3F505837A1', 'FFE2A4D3-267C-465F-B4B4-C7BB2582F1BC'))

there has two problems:

  1. I tried using user-defined type to set parameter type CREATE TYPE IDType AS (id uuid);, but I don't know how to call the function with table argument
  2. there has an error that says:
SQL Error [42703]: ERROR: column ip.depth does not exist
  Where: PL/pgSQL function f_itempath(record) line 3 at RETURN QUERY

what I expected is I can use the function normally and the argument can be supplied from other tables.

this is the full query that you can try: http://sqlfiddle.com/#!15/9caba/1
I made the query in DBEAVER app, it will have some different error message.
I suggest you can experiment with it outside sqlfiddle.

muazhari
  • 77
  • 5
  • Just the `WITH.....;` piece of your function returns an error ("ERROR: column "depth" does not exist Position: 68") which you should fix first. You cannot use a function if the definition of it is incorrect, so "what I expected is I can use the function normally" is ?? see: http://sqlfiddle.com/#!15/9caba/18 – Luuk Mar 27 '21 at 08:55

1 Answers1

2

The expression depth=1 tests if the column depth equals the value 1 and returns a boolean value. But you never give that boolean expression a proper name.

Additionally you can't add numbers to boolean values, so the expression depth=ip.depth + 1 tries to add 1 to a value of true or false - which fails obviously. If it did work, it would then compare that value with the value in the column depth again.

Did you intend to alias the value 1 with the name depth? Then you need to use 1 as depth and ip.depth + 1 as depth in the recursive part.

In the final select you have the same error - using boolean expressions instead of a column alias

It's also highly recommended to use explicit JOIN operators which were introduced in the SQL standard over 30 years ago.

Using PL/pgSQL to wrap a SQL query is also a bit of an overkill. A SQL function is enough.

Using an untyped record as a parameter seems highly dubious. It won't allow you to access columns using e.g. item.id. But given your example call, it seems you simply want to pass multiple IDs for the anchor (no-recursive) part of the query. That's better done using an array or a varadic parameter which allows listing multiple parameters with commas.

So you probably want something like this:

drop function f_itempath;
CREATE OR REPLACE FUNCTION f_itempath(variadic p_root_id uuid[])
  RETURNS TABLE (item_id uuid, depth integer)
as
$$  
  WITH recursive item_path AS (
    SELECT ic.parent_item_id, 1 as depth
    FROM item_combination ic
    WHERE ic.child_item_id = any(p_root_id) --<< no join needed to access the parameter
    UNION all
    SELECT ic.parent_item_id, ip.depth + 1
    FROM item_path ip
       JOIN item_combination ic ON ip.parent_item_id = ic.child_item_id
  )
  SELECT ip.parent_item_id as item_id, ip.depth 
  FROM item_path ip;
$$
language sql
stable;

Then you can call it like this (note: no parentheses around the parameters)

select * 
from f_itempath('55d6f516-7d8f-4df3-a4e5-1e3f505837a1', 'ffe2a4d3-267c-465f-b4b4-c7bb2582f1bc');

select * 
from f_itempath('55d6f516-7d8f-4df3-a4e5-1e3f505837a1', 'ffe2a4d3-267c-465f-b4b4-c7bb2582f1bc', 'df366232-f200-4254-bad5-94e11ea35379');

select * 
from f_itempath('55d6f516-7d8f-4df3-a4e5-1e3f505837a1');

  • I tried using user-defined type to set parameter type "CREATE TYPE IDType AS (id uuid);", but I don't know how to call the function with table argument, can you revise the answer using that type in parameter and demonstrate how to call the function? – muazhari Mar 27 '21 at 08:08
  • and also when I ran your answer there has an error that says "SQL Error [42P13]: ERROR: VARIADIC parameter must be an array". – muazhari Mar 27 '21 at 08:09
  • @muazhari: I have already fixed the variadic definition –  Mar 27 '21 at 08:12
  • Just declare the parameter as `variadic p_root_id idtype[])` then. Why do you create an extra _type_ to encapsulate a `uuid`? That's complete overkill. But If you really want a special data type, a [domain](https://www.postgresql.org/docs/current/sql-createdomain.html) would probably be a better choice. –  Mar 27 '21 at 08:14
  • what if I need to supply the argument from tables? how to implement that? – muazhari Mar 27 '21 at 08:17
  • 1
    Extra columns for what? In the output? Additional parameters? Please do not extend your questions to a completely new topic once you have an answer that solves your initial problem. If you have another question, then [ask](https://stackoverflow.com/questions/ask) another question –  Mar 27 '21 at 08:20
  • I mean that function will be used to another query and the argument is supplied from other tables. – muazhari Mar 27 '21 at 08:24
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230439/discussion-between-muazhari-and-a-horse-with-no-name). – muazhari Mar 27 '21 at 12:12