Change primary key in PostgreSQL table - postgresql

I have users table in my PostgreSQL 9.3.6 database with two columns: id and another_id. The id is a primary key, the another_id is just another integer column with unique constraint.
There are other tables that reference users by primary key.
Here's the users table description:
Table "public.users"
Column | Type | Modifiers | Storage | Stats target | Description
----------------------+--------------------------------+----------------------------------------+---------+--------------+-------------
id | integer | not null | plain | |
another_id | integer | not null | plain | |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"uniq_1483a5e93414710b" UNIQUE, btree (another_id)
Referenced by:
TABLE "foo_table" CONSTRAINT "fk_4affc6e5a76ed395" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
TABLE "bar_table" CONSTRAINT "fk_72936b1da76ed395" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
TABLE "baz_table" CONSTRAINT "fk_83adbaf0a76ed395" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
Here's foo_table description:
Table "public.foo_table"
Column | Type | Modifiers | Storage | Stats target | Description
--------------+--------------------------------+-----------------------------------------------+----------+--------------+-------------
id | integer | not null | plain | |
user_id | integer | | plain | |
Indexes:
"foo_table_pkey" PRIMARY KEY, btree (id)
"idx_e52ffdeea76ed395" btree (user_id)
Foreign-key constraints:
"fk_e52ffdeea76ed395" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
How do I replace primary key in PostgreSQL table from id column to another_id column and maintain data integrity?

I've spent some time and finally came up with a working solution.
I will publish it here for future reference.
Solution
First of all, you have three tables (foo_table, bar_table, baz_table) which are pointing to your users table by means of foreign keys (called user_id in all cases). You will need to replace the IDs stored in those columns from id to another_id. Here's how you can do it:
-- We are dropping the foreign key constraint on dependant table (in other case it will prevent us from updating the values)
ALTER TABLE foo_table DROP CONSTRAINT fk_e52ffdeea76ed395;
-- Then, we're swapping values in foreign key column from id to another_id
UPDATE foo_table T SET user_id = (SELECT another_id FROM users WHERE id = T.user_id);
-- And finally we're creating new foreign key constraint pointing to the another_id instead of id
ALTER TABLE foo_table ADD CONSTRAINT fk_e52ffdeea76ed395 FOREIGN KEY (user_id) REFERENCES users (another_id) ON DELETE CASCADE;
You will need to repeat the above queries for each dependent table.
After that, all dependent tables will point to your new another_id column.
In the end we will just need to replace the primary key:
-- 1. Dropping the original primary key
ALTER TABLE users DROP CONSTRAINT users_pkey
-- 2. Renaming existing index for another_id (optional)
ALTER INDEX uniq_1483a5e93414710b RENAME TO users_pkey
-- 3. Creating new primary key using existing index for another_id
ALTER TABLE users ADD PRIMARY KEY USING INDEX users_pkey
-- 4. Creating index for old id column (optional)
CREATE UNIQUE INDEX users_id ON users (id)
-- 5. You can drop the original sequence generator if you won't need it
DROP SEQUENCE users_id_seq
You can even drop the original id column if you want to.
I hope it will help someone.

Related

Oracle Primary Key to Postgres

I'm migrating tables from Oracle to Postgres.
When a Primary key is created on an Oracle table, it implicitly creates a unique index with the same name.
But there is no such index created in Postgres or it is not visible in data dictionary tables.
Postgres doesn't allow creating an index with the Primary key name. I want to know if a unique index is required in Postgres on the primary key column. Does it by any way alter query performance if I do not create unique index for primary key column? Thanks in advance.
That is not correct:
create table pk_test (id integer primary key);
\d pk_test
Table "public.pk_test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | not null |
Indexes:
"pk_test_pkey" PRIMARY KEY, btree (id)

PostgreSQL and Hibernate: On DELETE CASCADE not working

I have a named native query which looks so:
#NamedNativeQuery(name = "deleteRecipes", query = "DELETE FROM RECIPE WHERE rcp_acc_identifier IN (?1)")
These are the important parts of the recipe table:
Table "public.recipe"
Column | Type | Modifiers
-------------------------+-----------------------------+---------------------------------------------------------
rcp_pf_id_photodata | bigint |
rcp_pf_id_thumbnaildata | bigint |
Indexes:
"fk_rcp_pf_id_photodata_idx" btree (rcp_pf_id_photodata)
"fk_rcp_pf_id_thumbnaildata_idx" btree (rcp_pf_id_thumbnaildata)
Foreign-key constraints:
"fk_rcp_pf_id_photodata" FOREIGN KEY (rcp_pf_id_photodata) REFERENCES persistable_file(pf_id) ON DELETE CASCADE
"fk_rcp_pf_id_thumbnaildata" FOREIGN KEY (rcp_pf_id_thumbnaildata) REFERENCES persistable_file(pf_id) ON DELETE CASCADE
When I execute the query above, the recipes will be deleted but not the data from persistable_file. Why is this so?
Additional information: I didn't set the ON DELETE CASCADE while creating the database. I added it later by dropping both constraints from above at first and then I added the constraints again with the additional ON DELETE CASCADE directive. Is this maybe the problem why the data is not deleted?

How do I copy only 2 tables in Postgres and ignore foreign keys and constraints?

I need to copy 2 tables from production to development. They are the same except the production table has a bit more data in it. I tried pg_dump to dump from production and copy to development, but it complained about foreign key constraints. How do I turn off constraints temporarily? I don't want to copy the whole database because it's too much data, and I want to keep my test data and users.
ALTER TABLE
DROP INDEX
ERROR: cannot drop constraint foods_pkey on table foods because other objects depend on it
DETAIL: constraint fk_rails_7e399284de on table histories depends on index foods_pkey
constraint fk_rails_8d89280489 on table food_nutrients depends on index foods_pkey
Here are the two tables:
Table "public.foods"
Column | Type | Modifiers
------------------+-----------------------------+----------------------------------------------------
id | integer | not null default nextval('foods_id_seq'::regclass)
...
Indexes:
"foods_pkey" PRIMARY KEY, btree (id)
"index_foods_on_food_category_id" btree (food_category_id)
Foreign-key constraints:
"fk_rails_a28abb337f" FOREIGN KEY (food_category_id) REFERENCES food_categories(id)
Referenced by:
TABLE "histories" CONSTRAINT "fk_rails_7e399284de" FOREIGN KEY (food_id) REFERENCES foods(id)
TABLE "food_nutrients" CONSTRAINT "fk_rails_8d89280489" FOREIGN KEY (food_id) REFERENCES foods(id)
And
Table "public.food_categories"
Column | Type | Modifiers
------------+-----------------------------+--------------------------------------------------------------
id | integer | not null default nextval('food_categories_id_seq'::regclass)
...
Indexes:
"food_categories_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "foods" CONSTRAINT "fk_rails_a28abb337f" FOREIGN KEY (food_category_id) REFERENCES food_categories(id)
I also tried --disable-triggers but it didn't help.
You can first copy the "food_categories" table. After that you can copy the "foods" table.

PostgreSQL: Should I alter an existing index or create a new one?

I have a billing_infos table that has a column called order_id. It already has an index.
\d billing_infos
...
Indexes:
"billing_infos_pkey" PRIMARY KEY, btree (id)
"billing_infos_address_id_idx" btree (address_id)
"index_billing_infos_on_order_id" btree (order_id) //<--this one
However, I am not sure if this is a unique index. I have the task of making a unique index, but I'm not sure if I should change the one that's already there or create a new one. The order_id values should all be unique.
Should I create a new index or alter the existing one?
And how do I check to see if the existing indexes are unique?
It is probably least invasive to create a unique index concurrently. Note that using a CONSTRAINT is the recommended way to enforce uniqueness. A UNIQUE index is more useful if the columns being checked require a function to create the uniqueness. An example of the latter is using COALESCE() to prevent NULLs from bypassing the UNIQUE check.
Eg.
create unique index foo_col1_col2_uidx on foo (col1, coalesce(col2,-1));
In the example above, col2 is an integer column and is not defined as NOT NULL.
Example of creating unique index concurrently.
create unique index concurrently billing_infos_order_id_uidx on billing_infos (order_id);
The output in psql from \d for a UNIQUE index (I've named mine _uidx) and UNIQUE CONSTRAINT (_uc) looks like the following:
\d foo
Table "public.foo"
Column | Type | Modifiers
--------+-----------------------------+-----------
x | integer |
tstamp | timestamp without time zone |
col | text |
Indexes:
"foo_col_uidx" UNIQUE, btree (col) <<< unique index
"foo_tstamp_uc" UNIQUE CONSTRAINT, btree (tstamp) <<< unique constraint
"foo_idx" btree (x)
That is not a unique index.
Try creating a simple table which has a primary key, a column with a unique constraint, and a column with a normal index:
create table example (id integer primary key, alpha integer, beta integer, gamma integer);
alter table example add constraint alpha_unique unique (alpha);
create index beta_normal on example (beta);
create unique index gamma_unique on example (gamma);
If you use \d on it, the output is:
Table "public.example"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null
alpha | integer |
beta | integer |
gamma | integer |
Indexes:
"example_pkey" PRIMARY KEY, btree (id)
"alpha_unique" UNIQUE CONSTRAINT, btree (alpha)
"gamma_unique" UNIQUE, btree (gamma)
"beta_normal" btree (beta)
As you can see, when an index is unique, it says so. You can even see when the index implements a unique constraint.
So, what should you do? Firstly, don't add a unique index. Don't ever do that. If you want to impose a uniqueness constraint on a column, you do that by adding a unique constraint. A constraint, not an index. The clue is in the name.
Adding a unique index may well work, but it is the wrong thing to do as the PostgreSQL manual says:
The preferred way to add a unique constraint to a table is ALTER TABLE
... ADD CONSTRAINT. The use of indexes to enforce unique constraints
could be considered an implementation detail that should not be
accessed directly.
So, simply use the alter table ... add constraint syntax i use above to add the constraint.
Use PgAdmin tool to see the index structure.

Adding primary key changes column type

Our database currently doesn't define primary keys on any tables. All of the id columns are simply unique indexes. I'm dropping those indexes and replacing them with proper primary keys.
My problem: In Postgres 8.4.7, one table in particular changes the data type from bigint to integer when I add the primary key to the table.
I've got the following table definition:
psql=# \d events
Table "public.events"
Column | Type | Modifiers
-----------------------+--------------------------+-----------------------------------------------------
id | bigint | not null default nextval('events_id_seq'::regclass)
[more columns omitted]
Indexes:
"events_id_unique_pk" UNIQUE, btree (id)
Foreign-key constraints:
"events_clearing_event_ref_fk" FOREIGN KEY (clearing_event_id) REFERENCES events(id)
"events_event_configs_id_fk" FOREIGN KEY (event_config_id) REFERENCES event_configs(id)
"events_pdu_circuitbreaker_id_fk" FOREIGN KEY (pdu_circuitbreaker_id) REFERENCES pdu_circuitbreaker(id)
"events_pdu_id_fk" FOREIGN KEY (pdu_id) REFERENCES pdus(id) ON DELETE CASCADE
"events_pdu_outlet_id_fk" FOREIGN KEY (pdu_outlet_id) REFERENCES pdu_outlet(id)
"events_sensor_id_fk" FOREIGN KEY (sensor_id) REFERENCES sensors(id)
"events_user_id_fk" FOREIGN KEY (clearing_user_id) REFERENCES users(id)
Referenced by:
TABLE "events" CONSTRAINT "events_clearing_event_ref_fk" FOREIGN KEY (clearing_event_id) REFERENCES events(id)
TABLE "event_params" CONSTRAINT "events_params_event_id_fk" FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE
Triggers:
event_validate BEFORE INSERT OR UPDATE ON events FOR EACH ROW EXECUTE PROCEDURE event_validate()
This is what happens:
psql=# ALTER TABLE events ADD PRIMARY KEY (id);
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "events_pkey" for table "events"
ALTER TABLE
psql=# \d events
Table "public.events"
Column | Type | Modifiers
-----------------------+--------------------------+-----------------------------------------------------
id | integer | not null default nextval('events_id_seq'::regclass)
[more columns omitted]
Indexes:
"events_pkey" PRIMARY KEY, btree (id)
"events_id_unique_pk" UNIQUE, btree (id)
Foreign-key constraints:
"events_clearing_event_ref_fk" FOREIGN KEY (clearing_event_id) REFERENCES events(id)
"events_event_configs_id_fk" FOREIGN KEY (event_config_id) REFERENCES event_configs(id)
"events_pdu_circuitbreaker_id_fk" FOREIGN KEY (pdu_circuitbreaker_id) REFERENCES pdu_circuitbreaker(id)
"events_pdu_id_fk" FOREIGN KEY (pdu_id) REFERENCES pdus(id) ON DELETE CASCADE
"events_pdu_outlet_id_fk" FOREIGN KEY (pdu_outlet_id) REFERENCES pdu_outlet(id)
"events_sensor_id_fk" FOREIGN KEY (sensor_id) REFERENCES sensors(id)
"events_user_id_fk" FOREIGN KEY (clearing_user_id) REFERENCES users(id)
Referenced by:
TABLE "events" CONSTRAINT "events_clearing_event_ref_fk" FOREIGN KEY (clearing_event_id) REFERENCES events(id)
TABLE "event_params" CONSTRAINT "events_params_event_id_fk" FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE
Triggers:
event_validate BEFORE INSERT OR UPDATE ON events FOR EACH ROW EXECUTE PROCEDURE event_validate()
I considered a few workarounds, but I'd really rather know why it's happening. There are a few other tables that also use bigint, so I don't want to just hack a solution in place.
This is scripted with Liquibase, but it happens directly in the Postgres console too.
Update
Two other points:
I can create a simple table with a bigint id and a unique index on id, add the primary key, and the column type stays the same.
All tables are empty at the time execution.
Could it have something to do with the constraints?
That's pretty interesting. I can't reproduce it with version 9.1.0 (yes, I should upgrade too!). But then I don't know precisely how the original table and sequence were created.
This page seems to allude to a similar automatic change of types between SERIAL and INTEGER: http://grover.open2space.com/content/migrate-data-postgresql-and-maintain-existing-primary-key
Could it be something like creating the table using SERIAL instead of BIGSERIAL, and then forcing the type to BIGINT? Something in between the sequence and primary key manipulations might have reset it.
I wasn't able to reproduce this the next day, even after reproducing it multiple times with witnesses the first time it occurred. I'm chalking it up to gremlins.