This question already has answers here:
Is SELECT or INSERT in a function prone to race conditions?
(3 answers)
Closed 4 months ago.
I'm creating a function to insert new data if the data not found.
Currently I am unable to return the ID after inserting the data. I get an empty result: return empty id after insert
Here's the query:
CREATE OR REPLACE FUNCTION save_data_if_not_found(
input_data1 character varying,
input_data2 character varying,
input_data3 integer)
RETURNS SETOF integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
DECLARE save_id integer;
BEGIN
RETURN QUERY
SELECT id FROM save_data_if_not_found
WHERE data2 = input_data2 AND data3 = input_data3;
IF NOT FOUND THEN
RAISE INFO 'No id found. Create new';
INSERT INTO save_data_if_not_found (data1,data2,data3)
VALUES (input_data1,input_data2,input_data3)
RETURNING id INTO save_id;
ELSE
RAISE INFO 'Id found.';
END IF;
END;
$BODY$;
SELECT * FROM save_data_if_not_found ('john','doe',1);
RETURNING id INTO save_id - this is don't return set of records. This code given last inserted id and set it to variable save_id
RETURNING without into - this is gets set of records. And you must write RETURN QUERY clause in the top of INSERT INTO too.
Example:
CREATE OR REPLACE FUNCTION save_data_if_not_found(
input_data1 character varying,
input_data2 character varying,
input_data3 integer)
RETURNS SETOF integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
DECLARE save_id integer;
BEGIN
RETURN QUERY
SELECT id FROM save_data_if_not_found
WHERE data2 = input_data2 AND data3 = input_data3;
IF NOT FOUND THEN
RAISE INFO 'No id found. Create new';
RETURN QUERY
INSERT INTO save_data_if_not_found (data1,data2,data3)
VALUES (input_data1,input_data2,input_data3)
RETURNING id;
ELSE
RAISE INFO 'Id found.';
END IF;
END;
$BODY$;
Related
I am tryign to fetch data from dynamic table using Function but getting error: "SQL Error [42704]: ERROR: type "schemaName.p_dynamictablename" does not exist".
CREATE OR REPLACE FUNCTION schemaName."GetAllDataFromDynamicTable"(IN P_DynamicTableName text, IN id integer)
RETURNS SETOF schemaName."P_DynamicTableName"
AS $$
BEGIN
return query
SELECT * FROM schemaName."P_DynamicTableName" WHERE "Id" = id;
END;
$$
LANGUAGE plpgsql;
I am tryign to fetch data from dynamic table using Stored Procudure but getting error: "SQL Error [42704]: ERROR: type "ml.tableName" does not exist"
Note: able to fetch data from table. ex: select * from schemaName."TableName";
CREATE OR REPLACE FUNCTION schemaName."GetAllDataFromDynamicTable"(tableName character varying)
RETURNS SETOF schemaName."tableName"
as $$
BEGIN
return query
select * from schemaName."tableName";
END;
$$
LANGUAGE plpgsql;
As stated by #Adrian, dynamic sql could be a solution :
CREATE OR REPLACE FUNCTION GetAllDataFromDynamicTable(IN P_DynamicTableName text, IN id integer)
RETURNS SETOF record
AS $$
BEGIN
RETURN QUERY
EXECUTE FORMAT('SELECT * FROM %I WHERE id = %s', P_DynamicTableName, id) ;
END;
$$
LANGUAGE plpgsql;
But calling this function will lead to an error until you can explicitly define the output columns of the function. This is possible only in the case where all the tables to be queried dynamically have the same columns definition.
There is a workaround which consists in converting the output record into a json object :
CREATE OR REPLACE FUNCTION NewGetAllDataFromDynamicTable(IN P_DynamicTableName text, IN id integer)
RETURNS SETOF jsonb
AS $$
BEGIN
RETURN QUERY
EXECUTE FORMAT('SELECT to_jsonb(t.*) FROM %I AS t WHERE t.id = %s', P_DynamicTableName, id) ;
END;
$$
LANGUAGE plpgsql;
Then you can call the function in different ways :
SELECT t.* FROM NewGetAllDataFromDynamicTable ('test', 1) AS t will return a json object
SELECT (jsonb_populate_record(NULL :: "test", t.*)).* FROM NewGetAllDataFromDynamicTable ('test', 1) AS t will return the columns of the table test
see dbfiddle
I have created a function to delete multiple records.In our table contain id as type uuid.
We get the input is like array of ids.
CREATE OR REPLACE FUNCTION public.deletetVersion(item_list uuid[])
RETURNS TABLE(id uuid[])
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
BEGIN
RETURN QUERY
DELETE FROM version WHERE id = ANY(item_list);
END;
$BODY$;
SELECT * from deletetVersion(Array['b6ad1912-e4f1-4419-831a-c70df89ffd63','877898f0-2f3f-4890-a658-898e35ffee3a'])
But i got an error like:
Anyone please help me
ERROR: function deletetversion(text[]) does not exist
it is because the
Array['b6ad1912-e4f1-4419-831a-c70df89ffd63','877898f0-2f3f-4890-a658-898e35ffee3a']
is treated as text[]
try the following
Array['b6ad1912-e4f1-4419-831a-c70df89ffd63'::uuid,'877898f0-2f3f-4890-a658-898e35ffee3a'::uuid]
as a parameter to your function
for example
CREATE OR REPLACE FUNCTION public.test_uuid(item_list uuid[])
RETURNS TABLE(id uuid[])
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
BEGIN
RETURN QUERY
SELECT item_list;
END;
$BODY$;
SELECT * from test_uuid(Array['b6ad1912-e4f1-4419-831a-c70df89ffd63'::uuid])
In case of deletion
CREATE OR REPLACE FUNCTION public.test_uuid(item_list uuid[])
RETURNS VOID
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
BEGIN
RETURN QUERY
DELETE from tableName WHERE id = ANY(item_list);
END;
$BODY$;
Your function should return either setof uuid - i.e. a table of uuid-s - or uuid[]. I would prefer the first. You do not need PL/pgSQL, plain SQL is enough. So the function is:
create or replace function public.deletetVersion(item_list uuid[])
returns setof uuid language 'sql' as
$$
delete from version where id = any(item_list) returning id;
$$;
The version returning an array is a bit more complex:
create or replace function public.deletetVersion(item_list uuid[])
returns uuid[] language 'sql' as
$$
with t(d_id) as
(
delete from version where id = any(item_list) returning id
)
select array_agg(d_id) from t;
$$;
And - as #Ibrahimshamma says - you may need to cast the argument to uuid[].
I'm trying to insert data from one table to another in postgres using for...loop. The approach is given below.
DO LANGUAGE PLPGSQL $$
DECLARE
data record;
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop values data;<br>
END LOOP;
END;
$$
I've used record for the row iteration but couldn't find out how to insert that 'data' into 'for_loop' table. When I run this code it gives me the following error:
ERROR: syntax error at or near "data"
LINE 9: INSERT INTO for_loop values data;
^
Here are my two tables.
create table forall_data(
nid numeric(15,0)not null,
name varchar(15) not null,
city varchar(10) not null,
contact numeric(11,0) not null
);
create table for_loop(
nid numeric(15,0)not null,
name varchar(15) not null,
city varchar(10) not null,
contact numeric(11,0) not null
);
What should I try here to insert that 'data' record into 'for_loop' table? Thanks in advance.
'data' is untyped record, so I have to mention the column name to retrieve the value of this record.
DO LANGUAGE PLPGSQL $$
DECLARE
data record;
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop values (data.nid,data.name,data.city,data.contact);
END LOOP;
END;
$$
But using %rowtype or table type is more flexible and no need to mention the column names to retrieve column value from the variable
DO LANGUAGE PLPGSQL $$
DECLARE
data forall_data; --- or data forall_data%rowtype
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop select (data).*;
END LOOP;
END;
$$
cheers :)
use this code:
DO LANGUAGE PLPGSQL $$
DECLARE
rec record;
BEGIN
FOR rec IN SELECT * FROM budzet.forall_data
LOOP
INSERT INTO budzet.for_loop(nid, name , city , contact)
VALUES (rec.nid, rec.name , rec.city , rec.contact);
END LOOP;
END;
$$
You can try Loop with some exit condition.
DO LANGUAGE PLPGSQL $$
DECLARE
rec CURSOR FOR SELECT * FROM forall_data;
V_nid numeric;
V_name varchar(15);
V_city varchar(10);
V_contact numeric;
BEGIN
OPEN rec;
LOOP
FETCH rec INTO V_nid ,V_name ,V_city,V_contact;
EXIT WHEN(rec IS NULL);
INSERT INTO for_loop(nid, name , city , contact)
VALUES (V_nid , V_name , V_city , V_contact);
END LOOP;
CLOSE rec;
END;
$$
Hope it work for you.
EDIT: Alternately you can try this without using loop insert statement from one table and select statement from another table.
INSERT INTO for_loop(nid, name , city , contact)
select nid, name , city , contact FROM forall_data;
I've setup a Stored Procedure in PL/Proxy to make a query, and receive some RECORDs back.
In PL/Proxy:
CREATE OR REPLACE FUNCTION query_autocomplete(q text, i_id bigint)
RETURNS SETOF RECORD AS $$
CLUSTER 'autocompletecluster';
RUN ON i_id;
$$ LANGUAGE plproxy;
In each Partition:
CREATE OR REPLACE FUNCTION query_autocomplete(q text, i_id bigint)
RETURNS SETOF RECORD AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN EXECUTE q
LOOP
RETURN NEXT rec;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
As you've likely guessed, this is hitting a defined SERVER in PGSQL called 'autocompletecluster'. The query string that I'm sending through is as follows:
$sql = "SELECT * FROM autocomplete WHERE member_id = :memberId";
$query = $this->db->prepare("SELECT query_autocomplete('{$sql}',1234");
It's returning the following:
SQLSTATE[XX000]: Internal error: 7 ERROR: PL/Proxy function public.query_autocomplete(0): unsupported type
The table that query is hitting is defined as such:
CREATE TABLE autocomplete (
id character varying(100) NOT NULL,
extra_data hstore,
username character varying(254),
member_id bigint
);
What am I doing wrong?
The error strongly suggests that PL/Proxy doesn't support SETOF RECORD. Try instead defining your functions to return autocomplete%rowtype or, failing that, RETURNS TABLE (...) with a matching columns-set.
I have a function that returns a table of calculated values based on a customer id. I need to get values for all customers; I made a cursor but I can't make it return the set.
Customer table:
id name
---- ----
CN102 Dude
CN103 Guy
CN104 Mate
Function:
SELECT * FROM get_custom_fields('CN104');
name field_value
---- -----
POP 9
Z44 blue
POP 19
Please note there could be multiple rows with the same name.
This is my cursor:
CREATE OR REPLACE FUNCTION my_cursor ()
RETURNS SETOF RECORD AS $$
DECLARE
v_customer_rec RECORD;
v_pop RECORD;
BEGIN
FOR v_customer_rec IN SELECT ucn FROM customer LOOP
SELECT INTO v_pop field_value from get_custom_fields(v_customer_rec.ucn) where custom_field='POP';
RAISE NOTICE 'Customer % Value %', v_customer_rec.ucn,v_pop;
-- RETURN QUERY select field_value from get_custom_fields(v_customer_rec.ucn) where custom_field='POP';
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
This returns:
db=# select my_cursor();
NOTICE: Customer CN102 Value (5)
NOTICE: Customer CN103 Value (12)
NOTICE: Customer CN104 Value (9)
NOTICE: Customer CN104 Value (19)
my_cursor
-------------
(0 rows)
So I know it should work. But if use RETURN QUERY (as commented in the code) I get the following error:
ERROR: set-valued function called in context that cannot accept a set
CONTEXT: PL/pgSQL function "my_cursor" line 9 at RETURN QUERY
How can I make it return the values in a table or set?
I'm trying to get:
ucn field_value
----- -----------
CN102 5
CN103 12
CN104 9
CN104 19
Your (simplified) function could look like this:
CREATE OR REPLACE FUNCTION my_cursor()
RETURNS SETOF RECORD AS
$func$
DECLARE
_ucn text;
BEGIN
FOR _ucn IN
SELECT ucn FROM customer
LOOP
RETURN QUERY
SELECT *
FROM get_custom_fields(_ucn)
WHERE name = 'POP';
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
I am assuming data type text for ucn here.
But really, you should define the RETURN type, to avoid having to provide a column definition list with every call. If you just want the column field_value, and it is type text:
CREATE OR REPLACE FUNCTION my_cursor()
RETURNS SETOF text AS
$func$
DECLARE
_ucn text;
BEGIN
FOR _ucn IN
SELECT ucn FROM customer
LOOP
RETURN QUERY
SELECT field_value
FROM get_custom_fields(_ucn)
WHERE name = 'POP';
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
Or use RETURNS TABLE() for multiple columns per row:
CREATE OR REPLACE FUNCTION my_cursor()
RETURNS TABLE(ucn text, field_value text) AS
$func$
DECLARE
_ucn text;
BEGIN
FOR _ucn IN
SELECT c.ucn FROM customer c
LOOP
RETURN QUERY
SELECT g.ucn, g.field_value
FROM get_custom_fields(_ucn) g
WHERE g.name = 'POP';
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
Be aware that the columns of the RETURN type are visible in the function body. Table-qualify columns of the same name to avoid naming conflicts.