How to use UPDATE in PostgreSQL with variable table? - sql

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;

Related

PostgreSQL function returning multiple DIFFERENT datasets [duplicate]

Is it possible to return multiple result sets from a Postgres function, like in MSSQL:
CREATE PROCEDURE test
AS
SELECT * FROM first_table
SELECT * FROM second_table
A simpler way has been around since PostgreSQL 8.3:
CREATE FUNCTION test()
RETURNS SETOF first_table AS
$func$
BEGIN
RETURN QUERY
SELECT * FROM first_table;
RETURN QUERY
SELECT * FROM second_table; -- has to return same rowtype as first_table!
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM test();
Both result sets are appended to a single set returned from the function.
See the manual for RETURN QUERY.
CREATE OR REPLACE FUNCTION "pr_GetCustomersAndOrders"()
RETURNS SETOF refcursor AS
$BODY$DECLARE
customerRC refcursor;
orderRC refcursor;
BEGIN
open customerRC FOR
SELECT * FROM customers;
RETURN NEXT customerRC;
open orderRC FOR
SELECT * FROM orders;
RETURN NEXT orderRC;
RETURN;
END;$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION "pr_GetCustomersAndOrders"() OWNER TO postgres;
I.o.w. using refcursors :)
CREATE OR REPLACE FUNCTION public.TestReturnMultipleTales
(
param_coid integer,
ref1 refcursor,
ref2 refcursor
)
RETURNS SETOF refcursor
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
DECLARE
BEGIN
OPEN ref1 FOR SELECT * FROM dbo.tbl1 WHERE coid = param_coid;
RETURN NEXT ref1;
OPEN ref2 FOR SELECT * FROM dbo.tbl2 LIMIT 5;
RETURN NEXT ref2;
END;
$BODY$;
USE IN pgSQL Query:-
BEGIN;
SELECT football_players.show_cities_multiple(123456, 'Ref1', 'Ref2');
FETCH ALL IN "Ref1";
FETCH ALL IN "Ref2";
COMMIT;
SELECT football_players.show_cities_multiple(123456, 'Ref1', 'Ref2');
FETCH ALL IN "Ref1";
SELECT football_players.show_cities_multiple(123456, 'Ref1', 'Ref2');
FETCH ALL IN "Ref2";
If first_table and second_table have the same layout, you can also just use
SELECT * FROM first_table WHERE ...
UNION ALL
SELECT * FROM second_table WHERE ...
[EDIT: Thanks to a commenter (whose name is probably not "null" :) ) for pointing out that UNION ALL is faster than UNION.]
Yes.
Example:
test=# create function x () returns setof integer language plpgsql as $$ begin return next 1; return next 2; end $$;
CREATE FUNCTION
test=# select * from x();
x
---
1
2
(2 rows)
You can of course use an existing table/view or a custom type for the returned type.
Example using language SQL:
test=# create table customer (name varchar, birth_date date);
CREATE TABLE
test=# create function y () returns setof customer language sql as $$
select * from customer
union all
select * from customer
$$;
CREATE FUNCTION
test=# insert into customer values ('joe', now()::date);
INSERT 0 1
test=# insert into customer values ('jill', now()::date);
INSERT 0 1
test=# select * from y();
name | birth_date
------+------------
joe | 2009-04-16
jill | 2009-04-16
joe | 2009-04-16
jill | 2009-04-16
(4 rows)
See here for doc

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?

Extract specific Columns from PostgreSQL table and Do an update on its values

I have a PostgreSQL database and I need to do an update over values of specific Columns. The number of columns is so big and I need to do the same operation to different table So better to extract them dynamically.
More specifically I want to extract from the table all the columns whose names ends with "_suffix" and do an update on their values.
I started trying to make a script but I don't know if it is the right road!
SELECT columns.column_name
FROM information_schema.columns
WHERE columns.table_name = 'myInitialTable' AND columns.column_name like '%\_suffix%' AND columns.table_schema = 'public';
I created a view of this query and I used it in the following function :
CREATE OR REPLACE FUNCTION updatetable() RETURNS int4 AS
$BODY$
DECLARE r RECORD;
BEGIN
FOR r IN SELECT * from v_reduced_table LOOP
update myInitialTable
set r.column_name = case
when r.column_name = '' then NULL
when r.column_name = 'value1' or r.column_name = 'value2' then 'xxxxx'
else r.column_name end;
END LOOP;
return 1;
END;
$BODY$
LANGUAGE plpgsql;
SELECT updatetable() as output;
this query do a loop on every column ending with suffix and updates its values. but when I run it I get
ERROR: syntax error at or near "$1"
LINE 1: update myInitialTable set $1 = case when $2 = '' then NULL when ...
Any help is appreciated :)
In your function you need to use dynamic commands.
The funcion format() is often very helpful.
Example data:
create table my_table(col1_suffix text, col2_suffix text, col3_suffix text);
insert into my_table values ('a', 'b', 'c');
Example function:
CREATE OR REPLACE FUNCTION update_my_table() RETURNS void AS
$BODY$
DECLARE r RECORD;
BEGIN
FOR r IN
SELECT columns.column_name
FROM information_schema.columns
WHERE columns.table_name = 'my_table'
AND columns.column_name like '%\_suffix%'
AND columns.table_schema = 'public'
LOOP
EXECUTE(FORMAT($f$
UPDATE my_table
SET %s = CASE
WHEN '%s' = 'col1_suffix' THEN 'col1'
WHEN '%s' = 'col2_suffix' OR '%s' = 'col3_suffix' THEN 'xxxxx'
END;$f$, r.column_name, r.column_name, r.column_name, r.column_name));
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;
Usage:
select update_my_table();
select * from my_table;
col1_suffix | col2_suffix | col3_suffix
-------------+-------------+-------------
col1 | xxxxx | xxxxx
(1 row)

How can I update a table based on a plpgsql instruction loop on PostgreSQL 8.3.16?

I got a problem I not sure how to solve it so far...
I have two tables that are related to each other with a 1 x n relation. I will try to describe the more importants fields below:
Table One - company:id PK,companyname varchar;
Table Two - training: course varchar,companyid bigint FK,id PK;
The problem is: I would like to update the information on course field of the table training because there are many courses with the same name. My idea is use something like
for s in 1..n loop
update training set course = course || s;
end loop;
No need for a loop you can do this with plain SQL:
with numbered as (
select id,
row_number() over (order by id) as rn
from training
)
update training
set course = course||n.rn::text
from numbered n
where n.id = training.id;
The common table expression assigns a number for each row in the training table and that number is then used to generate the new course name.
UPDATE training
SET course = course || num
FROM (
SELECT generate_series(1, (
SELECT count(course)
FROM training
)) num
) t
I solved mine doubt creating this function below:
CREATE OR REPLACE FUNCTION changeName()
RETURNS VOID AS
$$
DECLARE
table1id table1.id%TYPE;
counter1 RECORD;
counter2 RECORD;
BEGIN
FOR counter1 IN SELECT repeated_column,foreign_keyid,COUNT(repeated_column) AS contagem
FROM table1 GROUP BY repeated_column,foreign_keyid HAVING COUNT(repeated_column) > 1 LOOP
FOR counter2 IN 1..counter1.contagem LOOP
SELECT id INTO table1id FROM table1 WHERE repeated_column IN(counter1.repeated_column) AND foreign_keyid = counter1.foreign_keyid;
UPDATE table1 SET repeated_column = repeated_column || ' (' || counter2 || ')' WHERE id = table1id;
END LOOP;
END LOOP;
END;
$$
LANGUAGE 'plpgsql'

Return multiple fields as a record in PostgreSQL with PL/pgSQL

I am writing a SP, using PL/pgSQL.
I want to return a record, comprised of fields from several different tables. Could look something like this:
CREATE OR REPLACE FUNCTION get_object_fields(name text)
RETURNS RECORD AS $$
BEGIN
-- fetch fields f1, f2 and f3 from table t1
-- fetch fields f4, f5 from table t2
-- fetch fields f6, f7 and f8 from table t3
-- return fields f1 ... f8 as a record
END
$$ language plpgsql;
How may I return the fields from different tables as fields in a single record?
[Edit]
I have realized that the example I gave above was slightly too simplistic. Some of the fields I need to be retrieving, will be saved as separate rows in the database table being queried, but I want to return them in the 'flattened' record structure.
The code below should help illustrate further:
CREATE TABLE user (id int, school_id int, name varchar(32));
CREATE TYPE my_type AS (
user1_id int,
user1_name varchar(32),
user2_id int,
user2_name varchar(32)
);
CREATE OR REPLACE FUNCTION get_two_users_from_school(schoolid int)
RETURNS my_type AS $$
DECLARE
result my_type;
temp_result user;
BEGIN
-- for purpose of this question assume 2 rows returned
SELECT id, name INTO temp_result FROM user where school_id = schoolid LIMIT 2;
-- Will the (pseudo)code below work?:
result.user1_id := temp_result[0].id ;
result.user1_name := temp_result[0].name ;
result.user2_id := temp_result[1].id ;
result.user2_name := temp_result[1].name ;
return result ;
END
$$ language plpgsql
Don't use CREATE TYPE to return a polymorphic result. Use and abuse the RECORD type instead. Check it out:
CREATE FUNCTION test_ret(a TEXT, b TEXT) RETURNS RECORD AS $$
DECLARE
ret RECORD;
BEGIN
-- Arbitrary expression to change the first parameter
IF LENGTH(a) < LENGTH(b) THEN
SELECT TRUE, a || b, 'a shorter than b' INTO ret;
ELSE
SELECT FALSE, b || a INTO ret;
END IF;
RETURN ret;
END;$$ LANGUAGE plpgsql;
Pay attention to the fact that it can optionally return two or three columns depending on the input.
test=> SELECT test_ret('foo','barbaz');
test_ret
----------------------------------
(t,foobarbaz,"a shorter than b")
(1 row)
test=> SELECT test_ret('barbaz','foo');
test_ret
----------------------------------
(f,foobarbaz)
(1 row)
This does wreak havoc on code, so do use a consistent number of columns, but it's ridiculously handy for returning optional error messages with the first parameter returning the success of the operation. Rewritten using a consistent number of columns:
CREATE FUNCTION test_ret(a TEXT, b TEXT) RETURNS RECORD AS $$
DECLARE
ret RECORD;
BEGIN
-- Note the CASTING being done for the 2nd and 3rd elements of the RECORD
IF LENGTH(a) < LENGTH(b) THEN
ret := (TRUE, (a || b)::TEXT, 'a shorter than b'::TEXT);
ELSE
ret := (FALSE, (b || a)::TEXT, NULL::TEXT);
END IF;
RETURN ret;
END;$$ LANGUAGE plpgsql;
Almost to epic hotness:
test=> SELECT test_ret('foobar','bar');
test_ret
----------------
(f,barfoobar,)
(1 row)
test=> SELECT test_ret('foo','barbaz');
test_ret
----------------------------------
(t,foobarbaz,"a shorter than b")
(1 row)
But how do you split that out in to multiple rows so that your ORM layer of choice can convert the values in to your language of choice's native data types? The hotness:
test=> SELECT a, b, c FROM test_ret('foo','barbaz') AS (a BOOL, b TEXT, c TEXT);
a | b | c
---+-----------+------------------
t | foobarbaz | a shorter than b
(1 row)
test=> SELECT a, b, c FROM test_ret('foobar','bar') AS (a BOOL, b TEXT, c TEXT);
a | b | c
---+-----------+---
f | barfoobar |
(1 row)
This is one of the coolest and most underused features in PostgreSQL. Please spread the word.
You need to define a new type and define your function to return that type.
CREATE TYPE my_type AS (f1 varchar(10), f2 varchar(10) /* , ... */ );
CREATE OR REPLACE FUNCTION get_object_fields(name text)
RETURNS my_type
AS
$$
DECLARE
result_record my_type;
BEGIN
SELECT f1, f2, f3
INTO result_record.f1, result_record.f2, result_record.f3
FROM table1
WHERE pk_col = 42;
SELECT f3
INTO result_record.f3
FROM table2
WHERE pk_col = 24;
RETURN result_record;
END
$$ LANGUAGE plpgsql;
If you want to return more than one record you need to define the function as returns setof my_type
Update
Another option is to use RETURNS TABLE() instead of creating a TYPE which was introduced in Postgres 8.4
CREATE OR REPLACE FUNCTION get_object_fields(name text)
RETURNS TABLE (f1 varchar(10), f2 varchar(10) /* , ... */ )
...
To return a single row
Simpler with OUT parameters:
CREATE OR REPLACE FUNCTION get_object_fields(_school_id int
, OUT user1_id int
, OUT user1_name varchar(32)
, OUT user2_id int
, OUT user2_name varchar(32)) AS
$func$
BEGIN
SELECT INTO user1_id, user1_name
u.id, u.name
FROM users u
WHERE u.school_id = _school_id
LIMIT 1; -- make sure query returns 1 row - better in a more deterministic way?
user2_id := user1_id + 1; -- some calculation
SELECT INTO user2_name
u.name
FROM users u
WHERE u.id = user2_id;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM get_object_fields(1);
You don't need to create a type just for the sake of this plpgsql function. It may be useful if you want to bind multiple functions to the same composite type. Else, OUT parameters do the job.
There is no RETURN statement. OUT parameters are returned automatically with this form that returns a single row. RETURN is optional.
Since OUT parameters are visible everywhere inside the function body (and can be used just like any other variable), make sure to table-qualify columns of the same name to avoid naming conflicts! (Better yet, use distinct names to begin with.)
Simpler yet - also to return 0-n rows
Typically, this can be simpler and faster if queries in the function body can be combined. And you can use RETURNS TABLE() (since Postgres 8.4, long before the question was asked) to return 0-n rows.
The example from above can be written as:
CREATE OR REPLACE FUNCTION get_object_fields2(_school_id int)
RETURNS TABLE (user1_id int
, user1_name varchar(32)
, user2_id int
, user2_name varchar(32)) AS
$func$
BEGIN
RETURN QUERY
SELECT u1.id, u1.name, u2.id, u2.name
FROM users u1
JOIN users u2 ON u2.id = u1.id + 1
WHERE u1.school_id = _school_id
LIMIT 1; -- may be optional
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM get_object_fields2(1);
RETURNS TABLE is effectively the same as having a bunch of OUT parameters combined with RETURNS SETOF record, just shorter.
The major difference: this function can return 0, 1 or many rows, while the first version always returns 1 row.
Add LIMIT 1 like demonstrated to only allow 0 or 1 row.
RETURN QUERY is simple way to return results from a query directly.
You can use multiple instances in a single function to add more rows to the output.
db<>fiddle here (demonstrating both)
Varying row-type
If your function is supposed to dynamically return results with a different row-type depending on the input, read more here:
Refactor a PL/pgSQL function to return the output of various SELECT queries
If you have a table with this exact record layout, use its name as a type, otherwise you will have to declare the type explicitly:
CREATE OR REPLACE FUNCTION get_object_fields
(
name text
)
RETURNS mytable
AS
$$
DECLARE f1 INT;
DECLARE f2 INT;
…
DECLARE f8 INT;
DECLARE retval mytable;
BEGIN
-- fetch fields f1, f2 and f3 from table t1
-- fetch fields f4, f5 from table t2
-- fetch fields f6, f7 and f8 from table t3
retval := (f1, f2, …, f8);
RETURN retval;
END
$$ language plpgsql;
You can achieve this by using simply as a returns set of records using return query.
CREATE OR REPLACE FUNCTION schemaName.get_two_users_from_school(schoolid bigint)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
begin
return query
SELECT id, name FROM schemaName.user where school_id = schoolid;
end;
$function$
And call this function as : select * from schemaName.get_two_users_from_school(schoolid) as x(a bigint, b varchar);
you can do this using OUT parameter and CROSS JOIN
CREATE OR REPLACE FUNCTION get_object_fields(my_name text, OUT f1 text, OUT f2 text)
AS $$
SELECT t1.name, t2.name
FROM table1 t1
CROSS JOIN table2 t2
WHERE t1.name = my_name AND t2.name = my_name;
$$ LANGUAGE SQL;
then use it as a table:
select get_object_fields( 'Pending') ;
get_object_fields
-------------------
(Pending,code)
(1 row)
or
select * from get_object_fields( 'Pending');
f1 | f
---------+---------
Pending | code
(1 row)
or
select (get_object_fields( 'Pending')).f1;
f1
---------
Pending
(1 row)
CREATE TABLE users(user_id int, school_id int, name text);
insert into users values (1, 10,'alice')
,(5, 10,'boy')
,(13, 10,'cassey')
,(17, 10,'delores')
,(4, 11,'elaine');
I setted the user_id as arbitrary int. The function input parameter is the school_id. So if the school_id is 10 you hope to get the following result:
user_id | name | user_id | name
---------+-------+---------+------
1 | alice | 5 | boy
So your query should be something like:
with a as (
select u1.user_id,
u1.name from users u1
where school_id = 10 order by user_id limit 1),
b as
(select u2.user_id,u2.name from users u2
where school_id = 10 order by user_id limit 1 offset 1 )
select * from a cross JOIN b ;
So let's wrap the query to the plpgsql function.
CREATE OR REPLACE FUNCTION
get_object_fields2(_school_id int)
RETURNS TABLE (user1_id int
, user1_name text
, user2_id int
, user2_name text)
LANGUAGE plpgsql AS
$func$
DECLARE countu integer;
BEGIN
countu := (
select count(*) from users where school_id = _school_id);
IF countu >= 2 THEN
RETURN QUERY
with a as (
select u1.user_id,
u1.name from users u1
where school_id = _school_id
order by user_id limit 1),
b as(
select u2.user_id,u2.name from users u2
where school_id = _school_id
order by user_id limit 1 offset 1 )
select * from a cross JOIN b;
elseif countu = 1 then
return query
select u1.user_id, u1.name,u1.user_id, u1.name
from users u1 where school_id = _school_id;
else
RAISE EXCEPTION 'not found';
end if;
END
$func$;