Syntax error at or near "array_append" in recursive function - sql

I'm newbie in plpgsql. What is wrong in my function declaration?
It's rises the error:
ERROR: syntax error at or near "array_append". LINE 19:
array_append(parent.childs , get_childs( ROW( child.id_pk, c... ***
The function:
CREATE TYPE category_return AS (
id_pk INT,
parent_id_fk INT,
name varchar,
path ltree,
childs json[]
);
------------------------
CREATE OR REPLACE FUNCTION get_childs ( parent category_return )
RETURNS category_return AS
$BODY$
DECLARE
child record;
BEGIN
FOR child IN
SELECT * FROM categories c WHERE c.parent_id_fk = parent.id_pk
LOOP
array_append(parent.childs , get_childs( ROW( child.id_pk, child.parent_if_fk, child.name, child.path, '[]'::json[]) ) );
END LOOP
RETURN row_to_json(parent);
END;
$BODY$ LANGUAGE plpgsql;

The cause for the error at hand is that you would have to assign the result of the array_append() to a variable. Like:
some_var := array_append(parent.childs , get_childs( ... ));
But the whole function is twisted in various ways and still wouldn't work.
No mention of what you are trying to achieve, so here is an educated guess (updated with recursive version):
CREATE OR REPLACE FUNCTION get_children (_id_pk int)
RETURNS TABLE (id_pk int, parent_if_fk int, name text, path int[]) AS
$func$
WITH RECURSIVE cte AS (
SELECT c.id_pk, c.parent_if_fk, c.name, ARRAY[c.id_pk] AS path
FROM categories c
WHERE c.parent_if_fk = $1
UNION ALL
SELECT c.id_pk, c.parent_if_fk, c.name, cte.path || c.id_pk
FROM cte
JOIN categories c ON c.parent_if_fk = c.id_pk
)
SELECT *
FROM cte
ORDER BY path
$func$ LANGUAGE sql;
Returns all rows in the hierarchy below the starting ID.
Use a recursive CTE in a plain SQL function. No plpgsql necessary.

Related

How can a function within a with block use a table defined in this same with block

WITH
FUNCTION f (a in integer)
RETURN INTEGER
IS
ret integer;
BEGIN
SELECT COUNT (*) into ret
FROM B;
RETURN ret +a;
END;
B(b1) as (select 1 from dual),
select f(3) from dual;
Oracle say :
[Error] Execution (6: 1): ORA-06552: PL/SQL: ORA-00942: table or view does not exist
dbfiddle (https://dbfiddle.uk/?rdbms=oracle_21&fiddle=ff90cec6ed25724bad9c215f0173607a)say :
ORA-00903: invalid table name
I think that Oracle doesn't know the table B because, she is defined after f is defined. But I can't change the order of what is defined in a with block. The functions must be defined before the table.
Does somebody has a solution to use in a function a table which is defined in the same with block?
I can't declare the B table inside the function, because I intend to create others function that use the table B. I don't want to defined B more than 2 times.
Declare B inside the function:
WITH FUNCTION f (a in integer)
RETURN INTEGER
IS
ret integer;
BEGIN
WITH B(b1) AS (
SELECT 1 FROM DUAL
)
SELECT COUNT (*) into ret
FROM B;
RETURN ret +a;
END;
SELECT f(3)
FROM DUAL;
Or, do the counting outside the function:
WITH FUNCTION f (
a IN PLS_INTEGER,
cnt IN PLS_INTEGER
)
RETURN INTEGER
IS
BEGIN
RETURN cnt +a;
END;
B (b1) AS (
SELECT 1 FROM DUAL
)
SELECT f(3, (SELECT COUNT(*) FROM b)) AS value
FROM DUAL;
Which both output:
VALUE
4
However
If you want to COUNT inside the function and reference a subquery factoring clause that is defined later then you are out of luck as you need to have the table defined before the function.
The syntax diagram states:
select::=
subquery::=
query_block::=
with_clause::=
subquery_factoring_clause::=
Although there is a clear chain in the syntax diagram SELECT, SUBQUERY, QUERY_BLOCK, WITH_CLAUSE, SUBQUERY_FACTORING_CLAUSE and back to SUBQUERY if, in practice you try to do something like nesting WITH clauses:
WITH B (b1) AS (
SELECT 1 FROM DUAL
),
C (value) AS (
WITH FUNCTION f (a in integer)
RETURN INTEGER
IS
ret integer;
BEGIN
SELECT COUNT (*) into ret
FROM B;
RETURN ret +a;
END f;
SELECT f(3)
FROM DUAL
)
SELECT value
FROM c;
You will get the error:
ORA-32034: unsupported use of WITH clause
db<>fiddle here

syntax error in PostgreSQL near customer_id

I am trying to get all the customer name based on their id for that I am using the PostgreSQL function and passing it an array of customer id's
DROP FUNCTION IF EXISTS public.list_of_customer(integer[]);
CREATE OR REPLACE FUNCTION public.list_of_customer(
IN customer_ids integer[])
RETURNS TABLE(id integer, name character varying) AS
$BODY$
BEGIN
RETURN QUERY
SELECT c.id, c.name
FROM public.customers AS c
WHERE c.id IN customer_ids;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
and call this function as:
select * from public.list_of_customer('{2740,2739,2738}');
You need = ANY():
CREATE OR REPLACE FUNCTION public.list_of_customer(
IN customer_ids integer[])
RETURNS TABLE(id integer, name character varying) AS
$BODY$
SELECT c.id
, c.name
FROM
public.customers AS c
WHERE
c.id = ANY($1); -- <- Use = ANY()
$BODY$
LANGUAGE sql VOLATILE; -- <- No need for plpgsql

A column definition list is required for functions returning "record" in Postgresql

In the Below Postgresql Function i am trying to get results from 2 different tables but it throws error ERROR: 42601: a column definition list is required for functions returning "record".Can anyone please help me.
CREATE OR REPLACE FUNCTION load_page_record(IN _session INT) RETURNS RECORD AS
$$
DECLARE r1 RECORD;
DECLARE r2 RECORD;
DECLARE RESULT RECORD;
BEGIN
SELECT array_agg(sq.*) AS arr INTO r1
FROM (SELECT user_id, user_name
FROM "user"
) sq;
SELECT array_agg(sq.*) AS arr INTO r2
FROM (SELECT client_id, client_name
FROM "clients"
) sq;
SELECT r1.arr, r2.arr INTO RESULT;
RETURN RESULT;
END;
$$ LANGUAGE plpgsql;
It returns a record,
so you should call the function as below,
select load_page_record(5);
The error come if you call it as a table
select * from load_page_record(5);
If you want to return a table place you query with join inside the body as follows,
CREATE OR REPLACE FUNCTION load_page_record1(IN _session INT)
RETURNS TABLE (column1 integer, column2 integer) as
$BODY$
SELECT column1, column2
FROM
table1 a
join
table2 b
ON a.id = b.id
$BODY$
LANGUAGE plpgsql;
try this, procedur return table
CREATE OR REPLACE FUNCTION load_page_record(IN _session INT)
RETURNS table(col1 record[],col2 record[]) AS
$BODY$
BEGIN
RETURN QUERY
select
(SELECT array_agg(sq.*)
FROM (SELECT user_id, user_name
FROM "user"
) sq
),
(SELECT array_agg(sq.*)
FROM (SELECT client_id, client_name
FROM "clients"
) sq
);
END;
$BODY$ LANGUAGE plpgsql stable;
edit: convert to text, try it
CREATE OR REPLACE FUNCTION load_page_record(IN _session INT)
RETURNS table(col1 text,col2 text) AS
$BODY$
BEGIN
RETURN QUERY
select
(SELECT array_agg(sq.*)
FROM (SELECT user_id, user_name
FROM "user"
) sq
)::text,
(SELECT array_agg(sq.*)
FROM (SELECT client_id, client_name
FROM "clients"
) sq
)::text;
END;
$BODY$ LANGUAGE plpgsql stable;
try with text:
CREATE OR REPLACE FUNCTION load_page_record(IN _session INT) RETURNS text AS
$$
DECLARE r1 RECORD;
DECLARE r2 RECORD;
DECLARE RESULT text;
BEGIN
SELECT array_agg(sq.*) AS arr INTO r1
FROM (SELECT 'fdfdfd','fdfdd'
) sq;
SELECT array_agg(sq.*) AS arr INTO r2
FROM (SELECT 'dsds','sdsd'
) sq;
SELECT r1.arr, r2.arr INTO RESULT;
RETURN RESULT;
END;
$$ LANGUAGE plpgsql;
and then simply:
select * from load_page_record(8);
but I hope you are aware of the fact that this instruction SELECT r1.arr, r2.arr INTO RESULT; will only assign the first column to RESULT?

Plpgsql : non recursive category tree function doesn't return any rows :(

So, here's the function that would return the products of a given category and its child categories. Its a 3 level tree. The function is fine, but when i run it, it says 0 rows returned. Any ideas?
EDIT: parents1 and parents2 are supposed to be arrays of the children of the parent category. Parents1 children of $1 , and parents2 children of all parents1 nodes.
CREATE OR REPLACE FUNCTION ecommerce.select_products_by_category(par bigint)
RETURNS SETOF ecommerce.product AS
$BODY$
declare
result ecommerce.product;
parents bigint[];
parents2 bigint[];
i int;
begin
parents := array(
select category_id from ecommerce.category where parent_id = par
);
return query select * from ecommerce.product where category_id = par;
for i in 1..array_upper(parents,1)
loop
return query select * from ecommerce.product where category_id = parents[i];
raise notice 'p %',parents[i];
end loop;
for i in 1..array_upper(parents,1)
loop
parents2 := array(
select category_id from ecommerce.category where parent_id = parents[i]
);
end loop;
for i in 1..array_upper(parents2,1)
loop
return query select * from ecommerce.product where category_id = parents2[i];
raise notice 'p2 %',parents2[i];
end loop;
--return query theset;
end ; $BODY$
and this is how i run it
SELECT * From ecommerce.select_products_by_category(
1
);
Your code cannot to work. The result of table function is related with any individual CALL of table function, not with global CALL of table function. Any table returning recursive function have to have use a pattern:
CREATE OR REPLACE FUNCTION foo(_parent_id integer)
RETURNS TABLE (node_id integer, node_val, parent_id integer) AS $$
BEGIN
FOR node_id, node_val, parent_id IN
SELECT f.node_id, f.node_val, f.parent_id
FROM footab f
WHERE f.parent_id = _parent_id
LOOP
RETURN NEXT;
/*
* Copy result of recursive call to function result
*/
RETURN QUERY SELECT * FROM foo(node_id);
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
When you use CTE, then then you can get result little bit faster and the code will be more readable:
WITH RECURSIVE t AS (
SELECT node_id, node_val, parent_id FROM footab
WHERE parent_id = <<root_id>>
UNION ALL
SELECT node_id, node_val, parent_id FROM footab, t
WHERE footab.parent_id = t.id
) SELECT * FROM t;

How to use UPDATE in PostgreSQL with variable table?

Example:
Table=["category1","category2","category3"]
for varTable in Table:
cr.execute('update varTable SET id=%s,WHERE id=%s)
........
....
How to do this loop?
Use dynamic SQL for that. The default is to use plpgsql with EXECUTE.
Create a function or use a DO statement for ad-hoc execution.
Dynamic SQL
CREATE OR REPLACE FUNCTION f_up(_new_id int, _old_id int)
RETURNS void AS
$BODY$
DECLARE
_tbl text[] := '{category1,category2,category3}';
t text;
BEGIN
FOREACH t IN ARRAY _tbl
LOOP
EXECUTE '
UPDATE ' || t || '
SET id = $1
WHERE id = $2'
USING _new_id, _old_id;
END LOOP;
END;
$BODY$ LANGUAGE plpgsql;
Call:
SELECT f_up(23, 45);
There are lots of similar answers on SO. Search for dynamic-sql, plpgsql and EXECUTE for more examples and explanation.
Plain SQL
If plpgsql is still black magic to you, you can solve this simple case quite effectively with a data-modifying CTE. Requires PostgreSQL 9.1 - for data-modifying CTE.
WITH vals AS (
SELECT 23 AS new_id, 45 AS old_id -- provide values once
)
, a AS (
UPDATE category1
SET id = v.new_id
FROM vals v
WHERE id = v.old_id
)
, b AS (
UPDATE category2
SET id = v.new_id
FROM vals v
WHERE id = v.old_id
)
UPDATE category3
SET id = v.new_id
FROM vals v
WHERE id = v.old_id;