Multiple table modification(same table has been used in that function) inside the Postgresql function - postgresql

create or replace function report(IN rows_count numeric DEFAULT 1000,
OUT out_rc_type rc_type)
RETURNS rc_type
AS
DECLARE
BEGIN
PERFORM ddl_exec_pkg.ddl_execute ( 'ALTER TABLE REPORT_UI DROP CONSTRAINT REPORT_ID');
PERFORM ddl_exec_pkg.ddl_execute ( 'ALTER TABLE REPORT_ALL_UI DROP CONSTRAINT REPORT_ID');
INSERT INTO
REPORT_UI (SELECT * FROM GE_REPORT);
PERFORM ddl_exec_pkg.ddl_execute ('ALTER TABLE REPORT_UI ADD CONSTRAINT REPORT_ID CHECK (1 = 1)');
PERFORM ddl_exec_pkg.ddl_execute ('ALTER TABLE REPORT_ALL_UI ADD CONSTRAINT REPORT_ID CHECK (1 = 1)');
END;
When in run the above code I am getting below error:
ERROR: cannot ALTER TABLE "REPORT_UI "
because it is being used by active queries in this session CONTEXT:
SQL statement "ALTER TABLE REPORT_UI ADD
CONSTRAINT REPORT_ID CHECK (1 = 1)"

Related

Alter table failing when i execute a function that modifys the table

I have written a generic function to change ids of students with wrong ids. Since the students table is referenced by other tables i had to drop the foreign keys and disable the triggers temporarily. This is my function.
CREATE OR REPLACE FUNCTION update_student_id(varchar, varchar) RETURNS varchar AS $$
DECLARE
old_id ALIAS FOR $1;
new_id ALIAS FOR $2;
msg varchar(120);
BEGIN
--disable triggers
ALTER TABLE students
DISABLE TRIGGER ALL;
ALTER TABLE studentdegrees
DISABLE TRIGGER ALL;
ALTER TABLE studentdegrees DROP CONSTRAINT studentdegrees_studentid_fkey;
UPDATE students
SET studentid=new_id
WHERE studentid=old_id;
ALTER TABLE studentdegrees
ADD CONSTRAINT studentdegrees_studentid_fkey
FOREIGN KEY (studentid)
REFERENCES students(studentid);
UPDATE studentdegrees
SET studentid=new_id
WHERE studentid=old_id;
UPDATE entitys
SET user_name=new_id
WHERE user_name=old_id;
--enable triggers
ALTER TABLE students
ENABLE TRIGGER ALL;
ALTER TABLE studentdegrees
ENABLE TRIGGER ALL;
msg:=old_id|| ' Changed to '||new_id;
RETURN msg;
END;
$$ LANGUAGE plpgsql;
When i call the function like this
SELECT update_student_id(
studentid, a.stdid)
FROM
(SELECT studentid, REPLACE(studentid, ' ', '') AS stdid
FROM students
WHERE studentid NOT IN (SELECT REPLACE(studentid, ' ', ''))) AS a
I get this error
ERROR: cannot ALTER TABLE "students" because it is being used by active queries in this session
CONTEXT: SQL statement "ALTER TABLE studentdegrees DROP CONSTRAINT studentdegrees_studentid_fkey"
PL/pgSQL function update_student_id(character varying,character varying) line 17 at SQL statement

Postgres create universal function on all table

I have a few db tables.
I want write universtal postgres function on copy rows to history tables
I have tables:
table1
table1_h
table2
table2_h
I wrote function (with help stackoverflow)
CREATE OR REPLACE FUNCTION copy_history_f() RETURNS TRIGGER AS
$BODY$
DECLARE
tablename_h text:= TG_TABLE_NAME || '_h';
BEGIN
EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(tablename_h) || ' VALUES (' || OLD.* ||')';
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
And functions was create, but after update is error.
ERROR: syntax error at or near ","
ROW 1: ...RT INTO table1_h VALUES ((12,,,0,,"Anto...
I know where is error in this insert but I don't know how I repair that.
Structure tables table1 and table1_h are identical but table1_h has one more column (id_h)
Can you help me, how I have create psql function?
Thnak you.
drop table if exists t;
drop table if exists t_h;
drop function if exists ftg();
create table t(i serial, x numeric);
insert into t(x) values(1.1),(2.2);
create table t_h(i int, x numeric);
create function ftg() returns trigger language plpgsql as $ftg$
declare
tablename_h text:= TG_TABLE_NAME || '_h';
begin
execute format($q$ insert into %I.%I select $1.*; $q$, TG_TABLE_SCHEMA, tablename_h) using old;
return null;
end $ftg$;
create trigger tg_t after delete on t for each row execute procedure ftg();
delete from t where i = 1;
select * from t_h;
dbfiddle
Update It solves your problem, but I think that you want to have a bit more info in your history tables. It will be more complex a bit:
drop table if exists t;
drop table if exists t_h;
drop function if exists ftg();
create table t(i serial, x numeric);
insert into t(x) values(1.1),(2.2);
create table t_h(
hi serial, -- just ID
hd timestamp, -- timestamp
hu text, -- user who made changes
ha text, -- action
i int, x numeric
);
create function ftg() returns trigger language plpgsql as $ftg$
declare
tablename_h text:= TG_TABLE_NAME || '_h';
begin
execute format(
$q$
insert into %I.%I
select
nextval(%L || '_hi_seq'),
clock_timestamp(),
current_user,
%L,
$1.*
$q$, TG_TABLE_SCHEMA, tablename_h, tablename_h, TG_OP) using old;
return null;
end $ftg$;
create trigger tg_t after delete or update on t for each row execute procedure ftg();
update t set x = x * 2;
update t set x = x * 2 where i = 2;
delete from t where i = 1;
select * from t_h;
dbfiddle
I assume you are inserting the 'old' values from table1 into table1_h.
The additional column is your problem. When you using an insert without naming columns you must use a matching number and type for the insert.
You must use column referencing.
eg.
Insert into table1_h(column1, column2, column3)
values (a,b,c)
Consider a default value for the additional column in table table1_h.

How can i Alter TABLE all column from not null to null by one query of PostgreSQL 9.1

I migrate my data from MySQL Database to PostgreSQL Database and by mistaken i have all my column set not-null in PostgreSQL database.
After this i am facing issue for inserting data and have to uncheck not null manually, so is there any way to do for all column in table ( except id(PRIMARY KEY) ).
i have this query also for single column but its also time consuming,
ALTER TABLE <table name> ALTER COLUMN <column name> DROP NOT NULL;
I don't think there is built in functionality for this. But it's easy to write a function to do this. For example:
CREATE OR REPLACE FUNCTION set_nullable(relation TEXT)
RETURNS VOID AS
$$
DECLARE
rec RECORD;
BEGIN
FOR rec IN (SELECT * FROM pg_attribute
WHERE attnotnull = TRUE AND attrelid=relation::regclass::oid AND attnum > 0 AND attname != 'id')
LOOP
EXECUTE format('ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL', relation, rec.attname);
RAISE NOTICE 'Set %.% nullable', relation, rec.attname;
END LOOP;
END
$$
LANGUAGE plpgsql;
Use it like this:
SELECT set_nullable('my_table');
Just perform a new CREATE TABLE noNULL using the same structure as your current table and modify the NULLable field you need.
Then insert from old table
INSERT INTO noNULL
SELECT *
FROM oldTable
then delete oldTable and rename noNull -> oldTable

PostgreSQL drop constraint with unknown name

I have an SQL script that needs to drop several constraints and restore them at the end, but the constraint names are auto-generated and will be different each time the script is run.
I know how to get the constraint name from the table names, but it doesn't seem possible to use this information in the drop statement.
select conname from pg_constraint where
conrelid = (select oid from pg_class where relname='table name')
and confrelid = (select oid from pg_class where relname='reference table');
alter table something drop constraint (some subquery) is a syntax error.
Ideally I would like to get the constraint name and store it in a variable, but it doesn't seem that Postgres supports that and I can't make it work with psql \set.
Is this even possible?
To dynamically drop & recreate a foreign key constraint, you could wrap it all in a function or use the DO command:
DO
$body$
DECLARE
_con text := (
SELECT quote_ident(conname)
FROM pg_constraint
WHERE conrelid = 'myschema.mytable'::regclass
AND confrelid = 'myschema.myreftable'::regclass
LIMIT 1 -- there could be multiple fk constraints. Deal with it ...
);
BEGIN
EXECUTE '
ALTER TABLE wuchtel12.bet DROP CONSTRAINT ' || _con;
-- do stuff here
EXECUTE '
ALTER TABLE myschema.mytable
ADD CONSTRAINT ' || _con || ' FOREIGN KEY (col)
REFERENCES myschema.myreftable (col)';
END
$body$
You must own the table to use ALTER TABLE.
Else you can create a function with LANGUAGE plpgsql SECURITY DEFINER (using the same body) and
ALTER FUNCTION foo() OWNER TO postgres;
postgres being a superuser here - or the owner of the table.
But be sure to know what the manual has to say about security.
The manual also has more on dynamic commands.
You can use stored procedure also.
CREATE OR REPLACE PROCEDURE public.p_costraint()
LANGUAGE plpgsql
AS $procedure$
DECLARE _constrint text;
begin
-- for dynamic change the constraint.
_constrint := (
SELECT quote_ident(conname)
FROM pg_constraint
WHERE conrelid = 'test.contacts'::regclass
AND confrelid = 'test.customers'::regclass
LIMIT 1 -- there could be multiple fk constraints. Deal with it ...
);
_constrint := _constrint || 'test';
EXECUTE '
ALTER TABLE test.contacts
ADD CONSTRAINT ' || _constrint || ' FOREIGN KEY (customer_id)
REFERENCES test.customers (customer_id)';
RAISE NOTICE 'hello, world!';
end
$procedure$;
In here. constraint name is used as a text variable.
You can just call it: call public.p_costraint();
It will return :
NOTICE: hello, world!
CALL

Adding 'serial' to existing column in Postgres

I have a small table (~30 rows) in my Postgres 9.0 database with an integer ID field (the primary key) which currently contains unique sequential integers starting at 1, but which was not created using the 'serial' keyword.
How can I alter this table such that from now on inserts to this table will cause this field to behave as if it had been created with 'serial' as a type?
Look at the following commands (especially the commented block).
DROP TABLE foo;
DROP TABLE bar;
CREATE TABLE foo (a int, b text);
CREATE TABLE bar (a serial, b text);
INSERT INTO foo (a, b) SELECT i, 'foo ' || i::text FROM generate_series(1, 5) i;
INSERT INTO bar (b) SELECT 'bar ' || i::text FROM generate_series(1, 5) i;
-- blocks of commands to turn foo into bar
CREATE SEQUENCE foo_a_seq;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
ALTER TABLE foo ALTER COLUMN a SET NOT NULL;
ALTER SEQUENCE foo_a_seq OWNED BY foo.a; -- 8.2 or later
SELECT MAX(a) FROM foo;
SELECT setval('foo_a_seq', 5); -- replace 5 by SELECT MAX result
INSERT INTO foo (b) VALUES('teste');
INSERT INTO bar (b) VALUES('teste');
SELECT * FROM foo;
SELECT * FROM bar;
You can also use START WITH to start a sequence from a particular point, although setval accomplishes the same thing, as in Euler's answer, eg,
SELECT MAX(a) + 1 FROM foo;
CREATE SEQUENCE foo_a_seq START WITH 12345; -- replace 12345 with max above
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
TL;DR
Here's a version where you don't need a human to read a value and type it out themselves.
CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
Another option would be to employ the reusable Function shared at the end of this answer.
A non-interactive solution
Just adding to the other two answers, for those of us who need to have these Sequences created by a non-interactive script, while patching a live-ish DB for instance.
That is, when you don't wanna SELECT the value manually and type it yourself into a subsequent CREATE statement.
In short, you can not do:
CREATE SEQUENCE foo_a_seq
START WITH ( SELECT max(a) + 1 FROM foo );
... since the START [WITH] clause in CREATE SEQUENCE expects a value, not a subquery.
Note: As a rule of thumb, that applies to all non-CRUD (i.e.: anything other than INSERT, SELECT, UPDATE, DELETE) statements in pgSQL AFAIK.
However, setval() does! Thus, the following is absolutely fine:
SELECT setval('foo_a_seq', max(a)) FROM foo;
If there's no data and you don't (want to) know about it, use coalesce() to set the default value:
SELECT setval('foo_a_seq', coalesce(max(a), 0)) FROM foo;
-- ^ ^ ^
-- defaults to: 0
However, having the current sequence value set to 0 is clumsy, if not illegal.
Using the three-parameter form of setval would be more appropriate:
-- vvv
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
-- ^ ^
-- is_called
Setting the optional third parameter of setval to false will prevent the next nextval from advancing the sequence before returning a value, and thus:
the next nextval will return exactly the specified value, and sequence advancement commences with the following nextval.
— from this entry in the documentation
On an unrelated note, you also can specify the column owning the Sequence directly with CREATE, you don't have to alter it later:
CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
In summary:
CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
Using a Function
Alternatively, if you're planning on doing this for multiple columns, you could opt for using an actual Function.
CREATE OR REPLACE FUNCTION make_into_serial(table_name TEXT, column_name TEXT) RETURNS INTEGER AS $$
DECLARE
start_with INTEGER;
sequence_name TEXT;
BEGIN
sequence_name := table_name || '_' || column_name || '_seq';
EXECUTE 'SELECT coalesce(max(' || column_name || '), 0) + 1 FROM ' || table_name
INTO start_with;
EXECUTE 'CREATE SEQUENCE ' || sequence_name ||
' START WITH ' || start_with ||
' OWNED BY ' || table_name || '.' || column_name;
EXECUTE 'ALTER TABLE ' || table_name || ' ALTER COLUMN ' || column_name ||
' SET DEFAULT nextVal(''' || sequence_name || ''')';
RETURN start_with;
END;
$$ LANGUAGE plpgsql VOLATILE;
Use it like so:
INSERT INTO foo (data) VALUES ('asdf');
-- ERROR: null value in column "a" violates not-null constraint
SELECT make_into_serial('foo', 'a');
INSERT INTO foo (data) VALUES ('asdf');
-- OK: 1 row(s) affected
Working for me in PostgreSQL 12.
It converts my existing int column to a serial column.
CREATE SEQUENCE table_name_id_seq;
ALTER TABLE table_name ALTER COLUMN id SET DEFAULT nextval('table_name_id_seq');
ALTER TABLE table_name ALTER COLUMN id SET NOT NULL;
ALTER SEQUENCE table_name_id_seq OWNED BY table_name.id;
SELECT setval('table_name_id_seq', (SELECT max(id) FROM table_name));
PostgreSQL 10 and later
Don't use serial. Thanks to the above comment.
ALTER TABLE mysmalltable
ALTER COLUMN id
ADD GENERATED BY DEFAULT AS IDENTITY;
If you use a DBMS you can set the default value on the column as Sequence.
It may be under advanced options.