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

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;

Related

idiomatic way to atomically create a table that as a record that is associated to other tables

I am coming from graph databases and postgres is still super foreign to me.
I have the following tables
CREATE TYPE runnerenum AS ENUM ('runner');
CREATE TABLE IF NOT EXISTS collections (
collectionid UUID PRIMARY KEY,
name VARCHAR(256) UNIQUE NOT NULL,
runner runnerenum NOT NULL,
runconfig JSONB
);
CREATE TABLE IF NOT EXISTS namedexprs(
namedexprid UUID PRIMARY KEY,
name VARCHAR(256) UNIQUE NOT NULL,
-- exprid UUID NOT NULL REFERENCES expressions(exprid),
collectionid UUID NOT NULL REFERENCES collections(collectionid) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS expressions(
exprid UUID PRIMARY KEY,
ast JSONB NOT NULL,
namedexprid UUID NOT NULL REFERENCES namedexprs(namedexprid) ON DELETE CASCADE
);
My question is what is the idiomatic way to create a collections atomically (while also creating associated expressions and namedexprs). Currently I am executing three separate queries and getting errors because of a foreign key violation.
Example of using DEFERRABLE:
CREATE TABLE parent_tbl (
parent_id integer PRIMARY KEY,
parent_val varchar UNIQUE
);
CREATE TABLE child_tbl (
child_id integer PRIMARY KEY,
parent_fk varchar REFERENCES parent_tbl (parent_val)
ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
child_val varchar
);
\d child_tbl
Table "public.child_tbl"
Column | Type | Collation | Nullable | Default
-----------+-------------------+-----------+----------+---------
child_id | integer | | not null |
parent_fk | character varying | | |
child_val | character varying | | |
Indexes:
"child_tbl_pkey" PRIMARY KEY, btree (child_id)
Foreign-key constraints:
"child_tbl_parent_fk_fkey" FOREIGN KEY (parent_fk) REFERENCES parent_tbl(parent_val) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
BEGIN;
INSERT INTO child_tbl VALUES (1, 'dog', 'cat');
SELECT * FROM child_tbl ;
child_id | parent_fk | child_val
----------+-----------+-----------
1 | dog | cat
(1 row)
SELECT * FROM parent_tbl ;
parent_id | parent_val
-----------+------------
(0 rows)
INSERT INTO parent_tbl VALUES (1, 'dog');
SELECT * FROM parent_tbl ;
parent_id | parent_val
-----------+------------
1 | dog
COMMIT;
The key to using DEFERRABLE is that the individual data entry statements need to be bundled into the same transaction, the BEGIN;/COMMIT;. This allows DEFERRABLE INITIALLY DEFERRED to work as the constraint check is deferred until the end of the transaction. For more ways you can manipulate this see SET CONSTRAINTS.

Renaming postgres table will drop existing indexes?

I am working on an ETL where we get data from hive and dump it to Postgres. Just to ensure the data is not corrupt I first store the data in a temp table (created as the main table with all the indexes and constraints) and if the data is validated copy it to the main table.
But it has been taking to long as the data is huge.
Once the data is validated I am now thinking of dropping the main table and then renaming the temp table to the main table.
Will renaming a table in Postgres drop the indexes,constraints and defaults defined on it?
It a word - no, it will not drop any indexes, constraints or defaults. Here's a quick demo:
db=> CREATE TABLE mytab (
id INT PRIMARY KEY,
col_uniq INT UNIQUE,
col_not_null INT NOT NULL DEFAULT 123
);
CREATE TABLE
db=> \d mytab
Table "public.mytab"
Column | Type | Modifiers
--------------+---------+----------------------
id | integer | not null
col_uniq | integer |
col_not_null | integer | not null default 123
Indexes:
"mytab_pkey" PRIMARY KEY, btree (id)
"mytab_col_uniq_key" UNIQUE CONSTRAINT, btree (col_uniq)
db=> ALTER TABLE mytab RENAME TO mytab_renamed;
ALTER TABLE
db=> \d mytab_renamed
Table "public.mytab_renamed"
Column | Type | Modifiers
--------------+---------+----------------------
id | integer | not null
col_uniq | integer |
col_not_null | integer | not null default 123
Indexes:
"mytab_pkey" PRIMARY KEY, btree (id)
"mytab_col_uniq_key" UNIQUE CONSTRAINT, btree (col_uniq)

In Postgres, how can I delete a row from table B when a row from table A is deleted?

I’m using Postgres 9.5.0. I have the following table
myproject=> \d my_objects;
Table "public.my_objects"
Column | Type | Modifiers
---------------------+-----------------------------+-------------------------------------
name | character varying |
day | date |
distance | double precision |
user_id | integer |
created_at | timestamp without time zone | not null
updated_at | timestamp without time zone | not null
distance_unit_id | integer |
import_completed | boolean |
id | character varying | not null default uuid_generate_v4()
linked_my_object_time_id | character varying |
web_crawler_id | integer |
address_id | character varying |
Indexes:
"my_objects_pkey" PRIMARY KEY, btree (id)
"index_my_objects_on_user_id_and_day_and_name" UNIQUE, btree (user_id, day, name)
"index_my_objects_on_user_id" btree (user_id)
"index_my_objects_on_web_crawler_id" btree (web_crawler_id)
Foreign-key constraints:
"fk_rails_5287d445c0" FOREIGN KEY (address_id) REFERENCES addresses(id) ON DELETE CASCADE
"fk_rails_970b2325bf" FOREIGN KEY (distance_unit_id) REFERENCES distance_units(id)
"fk_rails_dda3297b57" FOREIGN KEY (linked_my_object_time_id) REFERENCES my_object_times(id) ON DELETE CASCADE
"fk_rails_ebd32625bc" FOREIGN KEY (web_crawler_id) REFERENCES web_crawlers(id)
"fk_rails_fa07601dff" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
Right now, each my_object has an address field. What I would like is when I delete the my_object, the corresponding address entry be deleted as well. Without moving the address_id column out of the my_objects table, is it possible to set something up such that when I delete a row from the my_objects table, any corresponding address data is deleted as well? Obviously, the foreign key I have set up will not get the job done.
You can do this with a trigger:
CREATE OR REPLACE FUNCTION remove_address() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
DELETE FROM public.addresses WHERE id = OLD.address_id;
RETURN OLD;
END;$$;
CREATE TRIGGER remove_address
AFTER DELETE ON public.my_objects FOR EACH ROW
EXECUTE PROCEDURE remove_address()

Postgres: create foreign key relationship, getting 'Key is not present in table'?

I am working in Postgres 9.1 and I want to create a foreign key relationship for two tables that don't currently have one.
These are my tables:
# \d frontend_item;
Table "public.frontend_item"
Column | Type | Modifiers
-------------------+-------------------------+--------------------------------------------------------------------
id | integer | not null default nextval('frontend_prescription_id_seq'::regclass)
presentation_code | character varying(15) | not null
pct_code | character varying(3) | not null
Indexes:
"frontend_item_pkey" PRIMARY KEY, btree (id)
# \d frontend_pct;
Column | Type | Modifiers
------------+--------------------------+-----------
code | character varying(3) | not null
Indexes:
"frontend_pct_pkey" PRIMARY KEY, btree (code)
"frontend_pct_code_1df55e2c36c298b2_like" btree (code varchar_pattern_ops)
This is what I'm trying:
# ALTER TABLE frontend_item ADD CONSTRAINT pct_fk
FOREIGN KEY (pct_code) REFERENCES frontend_pct(code) ON DELETE CASCADE;
But I get this error:
ERROR: insert or update on table "frontend_item" violates
foreign key constraint "pct_fk"
DETAIL: Key (pct_code)=(5HQ) is not present in table "frontend_pct"
I guess this makes sense, because currently the frontend_pct table is empty, while the frontend_item has values in it.
Firstly, is the syntax of my ALTER TABLE correct?
Secondly, is there an automatic way to create the required values in frontend_pct? It would be great if there was some way to say to Postgres "create the foreign key, and insert values into the foreign key table if they don't exist".
Your syntax seems correct.
No, there is not an automatic way to insert the required values.
You can only do it manually before adding the constraint. In your case must be something like
INSERT INTO frontend_pct (code)
SELECT code FROM
(
SELECT DISTINCT pct_code AS code
FROM frontend_item
WHERE pct_code NOT IN (SELECT code FROM frontend_pct)
) AS a;
NOTICE:
The query can be heavy if you have lot of data..

See all indexes and appropriate columns for table

How to see all existed indexes for table? for example given table mytable, how to see his every index with appropriate columns?
Try this SQL
SELECT * FROM pg_indexes WHERE tablename = 'mytable';
In psql use the \d command:
postgres=> create table foo (id integer not null primary key, some_data varchar(20));
CREATE TABLE
postgres=> create index foo_data_idx on foo (some_data);
CREATE INDEX
postgres=> \d+ foo
Table "public.foo"
Column | Type | Modifiers | Storage | Stats target | Description
-----------+-----------------------+-----------+----------+--------------+------------
id | integer | not null | plain | |
some_data | character varying(20) | | extended | |
Indexes:
"foo_pkey" PRIMARY KEY, btree (id)
"foo_data_idx" btree (some_data)
Has OIDs: no
postgres=>
Other SQL tools have other means of displaying this information.