Dumping Data into an Existing Database
-
Let's consider a situation where we have a
.dump
file created from a database (see in this blog post how we do this). -
By applying a
.dump
file we can create all tables in an existing database, but the following is not appropriate:Create a new database
because none of our teammates would want to reconfigure the host, the username, the credentials, etc.
-
Therefore we come up with the next solution:
Destroy (remove) all tables in an existing database, and then apply
.dump
file to create all tables
Destructively Remove all Tables in a Database (Not removing the database)
DO $$ DECLARE r RECORD; BEGIN -- Drop tables FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; END LOOP; -- Drop enums FOR r IN ( SELECT t.typname FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = current_schema() GROUP BY t.typname ) LOOP EXECUTE 'DROP TYPE ' || quote_ident(r.typname) || ' CASCADE'; END LOOP; END $$;
Next for the sake of completeness let's truncate the commands in that previous blog post and apply it selectively:
Step 1. Clone a Database into a Dump File
export SOURCE_DB_USER= export SOURCE_DB_PASSWORD= export SOURCE_DB_HOST= export SOURCE_DB_NAME= echo "Cloning Remote DB ..." docker run --rm -v $(pwd):/backup \ -e PGPASSWORD=$SOURCE_DB_PASSWORD \ postgres:16 pg_dump \ -h $SOURCE_DB_HOST \ -U $SOURCE_DB_USER \ -d $SOURCE_DB_NAME \ --no-owner --format=custom --file=/backup/database_dump.dump
If you encounter problem of pgsql version, simply change the number in postgres:<version>
.
Here we omit the original table owner from old table (as it does not exist in the new table).
Step 2. (Optional) Create a Database in a PostgreSQL Server
Make sure an existing database server has been spin up. Skip this step if you are cloning data into an shared database (e.g., not local).
export TARGET_DB_HOST="host.docker.internal" export TARGET_DB_USER="pguser" export TARGET_DB_PASSWORD="pguser" export TARGET_DB_NAME="some-name" echo "Creating Local Database ..." docker run --rm \ -e PGPASSWORD=$TARGET_DB_PASSWORD \ postgres:15 createdb \ -h $TARGET_DB_HOST \ -U $TARGET_DB_USER \ $TARGET_DB_NAME
Step 3. Dump Data into an Existing Database
export TARGET_DB_HOST="host.docker.internal" export TARGET_DB_USER="pguser" export TARGET_DB_PASSWORD="pguser" export TARGET_DB_NAME="some-name" echo "Dumping DB Data Into $TARGET_DB_HOST/$TARGET_DB_NAME" docker run --rm -v $(pwd):/backup \ -e PGPASSWORD=$TARGET_DB_PASSWORD \ postgres:16 pg_restore \ -h $TARGET_DB_HOST \ -U $TARGET_DB_USER \ -d $TARGET_DB_NAME \ --if-exists \ --no-owner --no-acl \ -c /backup/database_dump.dump
Beware of the meaning of
/backup/database_dump.dump
Since we have mounted a volume referencing to the current working directory (see the -v $(pwd):/backup
), we have the following 1-1 correspondence:
$(pwd)/database_dump.dump
/backup/database_dump.dump
In other words, it refers to the local file ./database_dump.dump
inside of the directory due to the way we mount the volume.
Step 4. Custom Follow-up Actions After Dumping Data
export TARGET_DB_HOST="host.docker.internal" export TARGET_DB_USER="pguser" export TARGET_DB_PASSWORD="pguser" export TARGET_DB_NAME="some-name" echo "Follow-up Actions in Progress ..." cat <<EOF > temp_script.sql DELETE FROM "Quota_UsageCounter"; DELETE FROM "Quota_PersonalSeat"; DELETE FROM "Quota_TeamSeat"; DELETE FROM "Quota_Seat"; DELETE FROM "StripeProduct"; INSERT INTO "public"."StripeProduct" ("productName", "stripePriceId", "type") VALUES ('Powerful Billie', 'price_1PnuH6Rt6IuPFjtugpMi882V', 'PERSONAL_POWERFUL_BILLIE'), ('Handy Billie', 'price_1PnuH7Rt6IuPFjtuXl0RYniy', 'PERSONAL_HANDY_BILLIE'), ('Team Plan', 'price_1PxKSsRt6IuPFjtugx1WqZYw', 'TEAM_PLAN'); EOF docker run --rm -e PGPASSWORD=$TARGET_DB_PASSWORD -v $(pwd)/temp_script.sql:/tmp/script.sql postgres:15 psql -h $TARGET_DB_HOST -d $TARGET_DB_NAME -U $TARGET_DB_USER -f /tmp/script.sql rm temp_script.sql echo "Done"
Remove all Database by TRUNCATE TABLE <table> CASCADE
-
As mentioned in step 4 above we may wish to have follow-up actions.
-
Usually it is to forcefully clean a table and also to remove all entities from other tables referencing to our data.
There is a better command than DELETE FROM <table>
in this case!
TRUNCATE TABLE parent_table CASCADE;
Conversion From .dump
into .sql
.dump
into .sql
Why?
- For one reason we can observe what is actually done by the
.dump
script; - For another reason we can adjust the "data insertion" or table creation such as:
- making the insertion into
on conflict do nothing
or - changing the owner of the tables before creating one, etc
- making the insertion into
The Conversion Script from .dump
into .sql
.dump
into .sql
Given a database_dump.dump
file in the current diectory, we can execute
docker run --rm -v $(pwd):/backup \ postgres:15 pg_restore -f /backup/database_dump.sql -Fc /backup/database_dump.dump
to convert a database_dump.dump
file back to database_dump.sql
file.
Execute the .sql
file
.sql
fileTo execute this sql
script for backup we simply run
export TARGET_DB_HOST="host.docker.internal" export TARGET_DB_USER="pguser" export TARGET_DB_PASSWORD="pguser" export TARGET_DB_NAME="some-name" docker run --rm -v $(pwd):/backup \ -e PGPASSWORD=$TARGET_DB_PASSWORD \ postgres:15 psql \ -h $TARGET_DB_HOST \ -U $TARGET_DB_USER \ -d $TARGET_DB_NAME \ -f /backup/database_dump.sql
Typical Exmaple of a database_dump.sql
and how to Change it to avoid Duplicate key
database_dump.sql
and how to Change it to avoid Duplicate keyA typical backup database_dump.sql
script consists of the following block for each table:
-- -- Data for Name: Quota_UsageCounter; Type: TABLE DATA; Schema: public; Owner: james.lee -- COPY public."Quota_UsageCounter" (id, "seatId", ..., "summaryGenerated") FROM stdin; 718 601 0 1729262031656 2024-10-18 22:33:51 f 1729262029357 1729262029357 0 719 601 0 1729262403781 2024-10-18 22:40:03 f 1731940429357 1729262029357 0 730 603 0 1729337500635 2024-10-19 19:31:40 f 1729337499664 1729337499664 0 \.
Here we use ...
to omit the dummy names (not a valid sql
syntax!).
The COPY
clause can be replaced by
CREATE TEMP TABLE temp_table AS TABLE public."Quota_UsageCounter" WITH NO DATA; COPY temp_table (id, "seatId", ..., "summaryGenerated") FROM stdin; 718 601 0 1729262031656 2024-10-18 22:33:51 f 1729262029357 1729262029357 0 719 601 0 1729262403781 2024-10-18 22:40:03 f 1731940429357 1729262029357 0 730 603 0 1729337500635 2024-10-19 19:31:40 f 1729337499664 1729337499664 0 \. INSERT INTO public."Quota_UsageCounter" (SELECT * FROM temp_table) ON CONFLICT DO NOTHING; DROP TABLE temp_table;
User Role Management
Who is the owner of the table whose Original owner does not exist in the new Database?
Sometimes we clone from a database where the owner of the table does not exist in our new database, then
- the ownership of the table defaults to the user performing the restore operation (the user running
pg_restore
).
Most of the time this user will be of superuser
grade, but we wish to delegate the tables to an account with inferior power, for that:
View the tables and their owner
SELECT tablename, tableowner FROM pg_tables WHERE schemaname = 'public';

Change the owner
The following change the tables owned by postgres
to new_user
:
-- For all tables in public schema DO $$ DECLARE r record; BEGIN FOR r IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tableowner = 'postgres' LOOP EXECUTE 'ALTER TABLE public.' || quote_ident(r.tablename) || ' OWNER TO new_user'; END LOOP; END $$; -- For sequences DO $$ DECLARE r record; BEGIN FOR r IN SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'public' LOOP EXECUTE 'ALTER SEQUENCE public.' || quote_ident(r.sequence_name) || ' OWNER TO new_user'; END LOOP; END $$; -- For views DO $$ DECLARE r record; BEGIN FOR r IN SELECT viewname FROM pg_views WHERE schemaname = 'public' AND viewowner = 'postgres' LOOP EXECUTE 'ALTER VIEW public.' || quote_ident(r.viewname) || ' OWNER TO new_user'; END LOOP; END $$;
Create a user with minimal Development right (which can adjust schema)
CREATE USER new_user WITH PASSWORD 'new_password'; --- enable basic priviledges: GRANT USAGE ON SCHEMA public TO new_user; --- database level priviledges such as connect, create scehma, etc: GRANT ALL PRIVILEGES ON DATABASE new_db TO new_user; --- Schema-level priviledges: GRANT USAGE, CREATE, DROP ON SCHEMA public TO new_user; --- For using auto-increment function: GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO new_user; --- enable a user to create foreign key constraint: GRANT REFERENCES ON ALL TABLES IN SCHEMA public TO new_user; --- for future tables: ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT REFERENCES ON TABLES TO new_user; --- grant to all existing tables, including: SELECT, INSERT, UPDATE, --- DELETE, TRUNCATE, REFERENCES, TRIGGER: GRANT ALL ON ALL TABLES IN SCHEMA public TO new_user; --- or grant to all future tables more selectively: ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO testuser;
Note that to remove this user, one needs to:
REASSIGN OWNED BY new_user TO postgres;