Postgres remove constraint by column names - postgresql

Is there a way I can remove a constraint based on column names?
I have postgres 8.4 and when I upgrade my project the upgrade fails because a constraint was named something different in a different version.
Basically, I need to remove a constraint if it exists or I can just remove the constraint using the column names.
The name of the constraint is the only thing that has changed. Any idea if that's possible?
In this case, I need to remove "patron_username_key"
discovery=# \d patron
Table "public.patron"
Column | Type | Modifiers
--------------------------+-----------------------------+-----------
patron_id | integer | not null
create_date | timestamp without time zone | not null
row_version | integer | not null
display_name | character varying(255) | not null
username | character varying(255) | not null
authentication_server_id | integer |
Indexes:
"patron_pkey" PRIMARY KEY, btree (patron_id)
"patron_username_key" UNIQUE, btree (username, authentication_server_id)

You can use System Catalogs to find information bout constraints. Still, some constraints, like keys, are mentioned in the separate pg_constraint table, while others, like NOT NULL, are essentially a columns in the pg_attribute table.
For the keys, you can use this query to get a list of constraint definitions:
SELECT pg_get_constraintdef(c.oid) AS def
FROM pg_class t
JOIN pg_constraint c ON c.conrelid=t.oid
WHERE t.relkind='r' AND t.relname = 'table';
You can then filter out the ones that references your column and dynamically construct ALTER TABLE ... DROP CONSTRAINT ... statements.

Assuming that unique index is the result of adding a unique constraint, you can use the following SQL statement to remove that constraint:
do $$
declare
cons_name text;
begin
select constraint_name
into cons_name
from information_schema.constraint_column_usage
where constraint_schema = current_schema()
and column_name in ('authentication_server_id', 'username')
and table_name = 'patron'
group by constraint_name
having count(*) = 2;
execute 'alter table patron drop constraint '||cons_name;
end;
$$
I'm not sure if this will work if you have "only" added a unique index (instead of a unique constraint).
If you need to do that for more than 2 columns you also need to adjust the having count(*) = 2 part to match the number of columns in the column_name in .. condition.
(As you did not specify your PostgreSQL version I'm assuming the current version)

Related

How can I atomically swap table names and its references in postgres without any issues?

I want to swap names of two tables with each other. TableA <> TableB. Also atomically to avoid any issues of read/writes. I know I can do that in a transction.
I am creating the table using - CREATE TABLE TableB (LIKE TableA INCLUDING ALL);. Then I am also copying over the FKs from from A to B. I am then doing a INSERT INTO TABLEA.... to copy over the data as well (irrelevant for this). Once all of this is done, I rename the table using ALTER TABLE RENAME to swap the names TableA <> TableB in a transaction as well. So TableA becomes TableA_Old and TableB becomes the new TableA (example below)
However, this doesn't update the references to this new table in other tables, like TableC which still hold a FK against TableA_Old. When I do a \d+ on the newly renamed table TableA (which was TableB) before, I don't see the references. They still point to TableA_Old.
Here is an example
I create TableA
CREATE TABLE TableA (
id serial PRIMARY KEY
);
testdb=> \d TableA
Table "public.tablea"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('tablea_id_seq'::regclass)
Indexes:
"tablea_pkey" PRIMARY KEY, btree (id)
I create TableC with reference to TableA
CREATE TABLE TableC(
id serial PRIMARY KEY,
table_a_id INT,
CONSTRAINT table_a_table_c_fk
FOREIGN KEY(table_a_id)
REFERENCES TableA(id)
);
\d TableC
Table "public.tablec"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('tablec_id_seq'::regclass)
table_a_id | integer | | |
Indexes:
"tablec_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"table_a_table_c_fk" FOREIGN KEY (table_a_id) REFERENCES tablea(id)
You can see the reference
Now I create TableB that looks like TableA using a function
create or replace function create_table_like(source_table text, new_table text)
returns void language plpgsql
as $$
declare
rec record;
begin
execute format(
'create table %s (like %s including all)',
new_table, source_table);
for rec in
select oid, conname
from pg_constraint
where contype = 'f'
and conrelid = source_table::regclass
loop
execute format(
'alter table %s add constraint %s %s',
new_table,
replace(rec.conname, source_table, new_table),
pg_get_constraintdef(rec.oid));
end loop;
end $$;
select create_table_like('TableA', 'TableB');
\d TableB
testdb=> select create_table_like('TableA', 'TableB');
create_table_like
-------------------
(1 row)
testdb=> \d TableB
Table "public.tableb"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('tablea_id_seq'::regclass)
Indexes:
"tableb_pkey" PRIMARY KEY, btree (id)
Now I rename them
BEGIN;
ALTER TABLE TableA RENAME to TableA_OLD;
ALTER TABLE TableB RENAME to TableA;
COMMIT;
Now when I look at the structures, the reference is still to TableA_OLD from TableC
testdb=> \d TableA
Table "public.tablea"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('tablea_id_seq'::regclass)
Indexes:
"tableb_pkey" PRIMARY KEY, btree (id)
testdb=> \d TableB
testdb=> \d TableA_old
Table "public.tablea_old"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('tablea_id_seq'::regclass)
Indexes:
"tablea_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "tablec" CONSTRAINT "table_a_table_c_fk" FOREIGN KEY (table_a_id) REFERENCES tablea_old(id)
And TableC is pointing at the old table - REFERENCES tablea_old(id)
testdb=> \d TableC
Table "public.tablec"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('tablec_id_seq'::regclass)
table_a_id | integer | | |
Indexes:
"tablec_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"table_a_table_c_fk" FOREIGN KEY (table_a_id) REFERENCES tablea_old(id)
Is there a safe way to do this without dropping and recreating the constraints again?
I know i can also update the relfilenode pointer in pg_class and swap them. However, that is risky since TableB could have a slightly different looking schema. So, I am wondering if I can update some other table in the system catalogue or use a DDL that wouldn't require dropping the constraint.
The true identity of an object is its object ID, a number that is used to refer to the object in foreign key constraints an other internal database matters (with the notable exception of most function bodies). Renaming an object does not change its identity. You have to drop and re-create foreign key constraints.
If you use the rename table command then automatically will be renamed all dependencies objects, foreign keys, references linked to this table. For example:
ALTER TABLE public.table1 RENAME TO table2;

ERROR: more than one owned sequence found in Postgres

I'm setting up a identity column to my existing columns for the Patient table.
Here I would like to use GENERATED ALWAYS AS IDENTITY.
So I setup the identity column by using the following statement (previously it was serial):
ALTER TABLE Patient ALTER PatientId
ADD GENERATED ALWAYS AS IDENTITY (START WITH 1);
For the existing patient table I have a total of 5 records. (patientId 1 to 5)
When I insert a new record after the identity setup, it will throw an error like:
more than one owned sequence found
Even after resetting the identity column, I still get the same error.
ALTER TABLE Patient ALTER COLUMN PatientId RESTART WITH 6;
Let me know if you have any solutions.
Update: This bug has been fixed in PostgreSQL v12 with commit 19781729f78.
The rest of the answer is relevant for older versions.
A serial column has a sequence that is owned by the column and a DEFAULT value that gets the net sequence value.
If you try to change that column into an identity column, you'll get an error that there is already a default value for the column.
Now you must have dropped the default value, but not the sequence that belongs to the serial column. Then when you converted the column into an identity column, a second sequence owned by the column was created.
Now when you try to insert a row, PostgreSQL tries to find and use the sequence owned by the column, but there are two, hence the error message.
I'd argue that this is a bug in PostgreSQL: in my opinion, it should either have repurposed the existing sequence for the identity column or given you an error that there is already a sequence owned by the column, and you should drop it. I'll try to get this bug fixed.
Meanwhile, you should manually drop the sequence left behind from the serial column.
Run the following query:
SELECT d.objid::regclass
FROM pg_depend AS d
JOIN pg_attribute AS a ON d.refobjid = a.attrelid AND
d.refobjsubid = a.attnum
WHERE d.classid = 'pg_class'::regclass
AND d.refclassid = 'pg_class'::regclass
AND d.deptype <> 'i'
AND a.attname = 'patientid'
AND d.refobjid = 'patient'::regclass;
That should give you the name of the sequence left behind from the serial column. Drop it, and the identity column should behave as desired.
This is not an answer -- apologies, but this allows me to show, with a vivid image, the crazy behavior that I (unintentionally) uncovered this morning...
All I had to do was this:
alter TABLE db.generic_items alter column generic_item_id drop default;
alter TABLE db.generic_items alter column generic_item_id add generated by default as identity;
and now when scripting the table to SQL I get (abbreviated):
CREATE TABLE db.generic_items
(
generic_item_id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
generic_item_id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
generic_item_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT pk_generic_items PRIMARY KEY (generic_item_id),
)
I am thankful for the answer posted above, by Laurenz Albe! As he explains, just delete the sequence that was used for the serial default, and this craziness goes away and the table looks normal again.
Again, this is NOT AN ANSWER, but commenting did not let me add enough text.
Apology. Continues from my earlier comment(s).
This is what I executed and it shows, imo, that the manual fix is not sufficient, and with large tables, the repetitive trick I used (see below) would be impractical and potentially wrong because adopting an id belonging to a deleted row.
-- pls disregard the absence of 2 id rows, this is the final situation
\d vaste_data.studie_type
Table "vaste_data.studie_type"
Column | Type | Collation | Nullable | Default
--------+-----------------------+-----------+----------+----------------------------------
id | integer | | not null | generated by default as identity
naam | character varying(25) | | not null |
Indexes:
"pk_tstudytype_tstudytype_id" PRIMARY KEY, btree (id)
Referenced by:
TABLE "stuwadoors" CONSTRAINT "fk_t_stuwadoors_t_studytype" FOREIGN KEY (study_type_id) REFERENCES vaste_data.studie_type(id)
TABLE "psux" CONSTRAINT "study_studytype_fk" FOREIGN KEY (studie_type_id) FOREIGN KEY (studie_type_id) REFERENCES vaste_data.studie_type(id)
alter table vaste_data.studie_type alter column id drop default;
ALTER TABLE
alter table vaste_data.studie_type alter column id add generated by default as identity;
ALTER TABLE
-- I chose to show both sequences so I could try to drop either one.
SELECT d.objid::regclass
FROM pg_depend AS d
JOIN pg_attribute AS a ON d.refobjid = a.attrelid AND
d.refobjsubid = a.attnum
WHERE d.classid = 'pg_class'::regclass
AND d.refclassid = 'pg_class'::regclass
AND a.attname = 'id'
AND d.refobjid = 'vaste_data.studie_type'::regclass;
objid
-----------------------------------------
vaste_data.studie_type_id_seq
vaste_data.tstudytype_tstudytype_id_seq
(2 rows)
drop sequence vaste_data.studie_type_id_seq;
ERROR: cannot drop sequence vaste_data.studie_type_id_seq because column id of table vaste_data.studie_type requires it
HINT: You can drop column id of table vaste_data.studie_type instead.
\d vaste_data.studie_type_id_seq
Sequence "vaste_data.studie_type_id_seq"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
---------+-------+---------+------------+-----------+---------+-------
integer | 1 | 1 | 2147483647 | 1 | no | 1
Sequence for identity column: vaste_data.studie_type.id
alter sequence vaste_data.studie_type_id_seq start 6;
ALTER SEQUENCE
drop sequence vaste_data.tstudytype_tstudytype_id_seq;
DROP SEQUENCE
insert into vaste_data.studie_type (naam) values('Overige leiding');
ERROR: duplicate key value violates unique constraint "pk_tstudytype_tstudytype_id"
DETAIL: Key (id)=(1) already exists.
...
ERROR: duplicate key value violates unique constraint "pk_tstudytype_tstudytype_id"
DETAIL: Key (id)=(5) already exists.
insert into vaste_data.studie_type (naam) values('Overige leiding');
INSERT 0 1

PostgreSQL primary key id datatype from serial to bigserial?

I did some research but can't find the exact answer that I look for. Currently I have a primary key column 'id' which is set to serial but I want to change it to bigserial to map to Long in Java layer. What is the best way to achieve this considering this is a existing table? I think my Postgres version is 10.5. Also I am aware that both serial and bigserial are not a data type.
In Postgres 9.6 or earlier the sequence created by a serial column already returns bigint. You can check this using psql:
drop table if exists my_table;
create table my_table(id serial primary key, str text);
\d my_table
Table "public.my_table"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+--------------------------------------
id | integer | | not null | nextval('my_table_id_seq'::regclass)
str | text | | |
Indexes:
"my_table_pkey" PRIMARY KEY, btree (id)
\d my_table_id_seq
Sequence "public.my_table_id_seq"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
Owned by: public.my_table.id
So you should only alter the type of the serial column:
alter table my_table alter id type bigint;
The behavior has changed in Postgres 10:
Also, sequences created for SERIAL columns now generate positive 32-bit wide values, whereas previous versions generated 64-bit wide values. This has no visible effect if the values are only stored in a column.
Hence in Postgres 10+:
alter sequence my_table_id_seq as bigint;
alter table my_table alter id type bigint;
-- backup table first
CREATE TABLE tablenamebackup as select * from tablename ;
--add new column idx
alter table tablename add column idx bigserial not null;
-- copy id to idx
update tablename set idx = id ;
-- drop id column
alter table tablename drop column id ;
-- rename idx to id
alter table tablename rename column idx to id ;
-- Reset Sequence to max + 1
SELECT setval(pg_get_serial_sequence('tablename', 'id'), coalesce(max(id)+1, 1), false) FROM tablename ;

Set the value of a column to its default value

I have few existing tables in which I have to modify various columns to have a default value.
How can I apply the default value to old records which are NULL, so that the old records will be consistent with the new ones
ALTER TABLE "mytable" ALTER COLUMN "my_column" SET DEFAULT NOW();
After modifying table looks something like this ...
Table "public.mytable"
Column | Type | Modifiers
-------------+-----------------------------+-----------------------------------------------
id | integer | not null default nextval('mytable_id_seq'::regclass)
....
my_column | timestamp(0) with time zone | default now()
Indexes:
"mytable_pkey" PRIMARY KEY, btree (id)
Is there a simple to way to have all columns which are currently null and also which have a default value to be set to the default value ?
Deriving from insert into:
For clarity, you can also request default values explicitly, for individual columns or for the entire row:
INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', DEFAULT);
INSERT INTO products DEFAULT VALUES;
I just tried this, and it is as simple as
update mytable
set my_column = default
where my_column is null
See sqlfiddle
Edit: olaf answer is easiest and correct way of doing this however the below also is viable solution for most cases.
For a each column it is easy to use the information_schema and get the default value of a column and then use that in a UPDATE statement
UPDATE mytable set my_column = (
SELECT column_default
FROM information_schema.columns
WHERE (table_schema, table_name, column_name) = ('public', 'mytable','my_column')
)::timestamp
WHERE my_column IS NULL;
Note the sub-query must by typecast to the corresponding column data type .
Also this statement will not evaluate expressions as column_default will be of type character varying it will work for NOW() but not for expressions like say (NOW()+ interval ' 7 days')
It is better to get expression and validate it then apply it manually

Altering a parent table in Postgresql 8.4 breaks child table defaults

The problem: In Postgresql, if table temp_person_two inherits fromtemp_person, default column values on the child table are ignored if the parent table is altered.
How to replicate:
First, create table and a child table. The child table should have one column that has a default value.
CREATE TEMPORARY TABLE temp_person (
person_id SERIAL,
name VARCHAR
);
CREATE TEMPORARY TABLE temp_person_two (
has_default character varying(4) DEFAULT 'en'::character varying NOT NULL
) INHERITS (temp_person);
Next, create a trigger on the parent table that copies its data to the child table (I know this appears like bad design, but this is a minimal test case to show the problem).
CREATE FUNCTION temp_person_insert() RETURNS trigger
LANGUAGE plpgsql
AS '
BEGIN
INSERT INTO temp_person_two VALUES ( NEW.* );
RETURN NULL;
END;
';
CREATE TRIGGER temp_person_insert_trigger
BEFORE INSERT ON temp_person
FOR EACH ROW
EXECUTE PROCEDURE temp_person_insert();
Then insert data into parent and select data from child. The data should be correct.
INSERT INTO temp_person (name) VALUES ('ovid');
SELECT * FROM temp_person_two;
person_id | name | has_default
-----------+------+-------------
1 | ovid | en
(1 row )
Finally, alter parent table by adding a new, unrelated column. Attempt to insert data and watch a "not-null constraint" violation occur:
ALTER TABLE temp_person ADD column foo text;
INSERT INTO temp_person(name) VALUES ('Corinna');
ERROR: null value in column "has_default" violates not-null constraint
CONTEXT: SQL statement "INSERT INTO temp_person_two VALUES ( $1 .* )"
PL/pgSQL function "temp_person_insert" line 2 at SQL statement
My version:
testing=# select version();
version
-------------------------------------------------------------------------------------------------------
PostgreSQL 8.4.17 on x86_64-pc-linux-gnu, compiled by GCC gcc-4.4.real (Debian 4.4.5-8) 4.4.5, 64-bit
(1 row)
It's there all the way to 9.3, but it's going to be tricky to fix, and I'm not sure if it's just undesirable behaviour rather than a bug.
The constraint is still there, but look at the column-order.
Table "pg_temp_2.temp_person"
Column | Type | Modifiers
-----------+-------------------+-----------------------------------------------------------------
person_id | integer | not null default nextval('temp_person_person_id_seq'::regclass)
name | character varying |
Number of child tables: 1 (Use \d+ to list them.)
Table "pg_temp_2.temp_person_two"
Column | Type | Modifiers
-------------+----------------------+-----------------------------------------------------------------
person_id | integer | not null default nextval('temp_person_person_id_seq'::regclass)
name | character varying |
has_default | character varying(4) | not null default 'en'::character varying
Inherits: temp_person
ALTER TABLE
Table "pg_temp_2.temp_person_two"
Column | Type | Modifiers
-------------+----------------------+-----------------------------------------------------------------
person_id | integer | not null default nextval('temp_person_person_id_seq'::regclass)
name | character varying |
has_default | character varying(4) | not null default 'en'::character varying
foo | text |
Inherits: temp_person
It works in your first example because you are effectively doing:
INSERT INTO temp_person_two (person_id,name)
VALUES (person_id, name)
BUT look where your new column is added in the child table - at the end! So you end up with
INSERT INTO temp_person_two (person_id,name,has_default)
VALUES (person_id, name, foo)
rather than what you hoped for:
INSERT INTO temp_person_two (person_id,name,foo)...
So - what's the correct behaviour here? If PostgreSQL shuffled the columns in the child table that could break code. If it doesn't, that can also break code. As it happens, I don't think the first option is do-able without substantial PG code changes, so it's unlikely to do that in the medium term.
Moral of the story: explicitly list your INSERT column-names.
Could take a while by hand. You know any languages with regexes? ;-)
It's not a bug. NEW.* expands to the values of each column in the new row, so you're doing INSERT INTO temp_person_two VALUES ( NEW.person_id, NEW.name, NEW.foo ), the last of which is indeed NULL if you didn't specify it (and wrong if you did).
I'm surprised it even works before you added the new column, since the number of values doesn't match the number of fields in the child table. Presumably it assumes the default for missing trailing values.