Postgres Migrations
When aerich
or alembic
auto-generated migration files aren’t enough.
Summary of the basic DDL/DML statements used to keep track of the Postrges DB migrations, followed by a couple of use cases.
General DDL / DML
CREATE
/ INSERT
1CREATE EXTENSION IF NOT EXISTS postgis;
2CREATE INDEX location_gidx ON product USING gist (location);
3CREATE UNIQUE INDEX product_name_supplier_uidx ON product (name, supplier);
4CREATE TYPE orderstatus AS ENUM('PROCESSED', 'SHIPPED', 'DELIVERED', 'CANCELLED');
5-- upsert
6INSERT INTO product (name, supplier_id, location, status)
7 VALUES ('NOTEBOOK-A6', 'Muji', ST_GeomFromText('POINT(12.3 4.56)', 4326), 'SHIPPED')
8 ON CONFLICT ON CONSTRAINT product_name_supplier_uc DO
9 UPDATE SET location=EXCLUDED.location, status=EXCLUDED.status;
ALTER
/ UPDATE
1ALTER SEQUENCE product_id_seq RESTART WITH 1453;
2ALTER TABLE product ADD CONSTRAINT product_name_supplier_uc UNIQUE USING INDEX product_name_supplier_uidx;
3ALTER TABLE product ADD CONSTRAINT product_supplier_fk FOREIGN KEY (supplier_id) REFERENCES supplier (id) ON DELETE CASCADE;
4ALTER TABLE product ALTER COLUMN description TYPE VARCHAR(2000) USING description::VARCHAR(2000); --longer varchar
5ALTER TABLE product ALTER COLUMN status TYPE orderstatus USING status::text::orderstatus;
6ALTER TYPE orderstatus ADD VALUE 'OUT FOR DELIVERY';
7ALTER TYPE orderstatus RENAME TO orderstatus_tmp;
8UPDATE product SET orderstatus = NULL WHERE status = 'OUT FOR DELIVERY';
DROP
/ DELETE
1DROP DATABASE my_db;
2DROP INDEX product_name_supplier_uid;
3ALTER TABLE product DROP CONSTRAINT product_name_supplier_uc;
4DROP TYPE orderstatus_tmp;
5DELETE FROM product WHERE id=1234;
Use cases
Add / delete column
1--upgrade
2ALTER TABLE product ADD COLUMN price NUMERIC NOT NULL CONSTRAINT positive_price CHECK (price > 0);
3--downgrade
4ALTER TABLE product DROP COLUMN price;
Rename column
1--upgrade
2ALTER TABLE product RENAME COLUMN description TO details;
3--downgrade
4ALTER TABLE product RENAME COLUMN details TO description;
Change column type
Character length
1--upgrade - add characters to description column
2ALTER TABLE product ALTER COLUMN description TYPE VARCHAR(2000) USING description::VARCHAR(2000);
3--downgrade - remove characters from description column
4UPDATE product SET description = LEFT(description, 200) WHERE CHAR_LENGTH(description) > 200;
5ALTER TABLE product ALTER COLUMN description TYPE VARCHAR(200) USING description::VARCHAR(200);
Enum
1--upgrade - add a value to orderstatus type
2ALTER TYPE orderstatus ADD VALUE 'OUT FOR DELIVERY';
3--downgrade - remove a value from orderstatus type
4CREATE TYPE old_orderstatus AS ENUM('PROCESSED', 'SHIPPED', 'DELIVERED', 'CANCELLED');
5UPDATE product SET status = NULL WHERE status = 'OUT FOR DELIVERY';
6ALTER TABLE product ALTER COLUMN status TYPE old_orderstatus USING status::text::old_orderstatus;
7DROP TYPE orderstatus;
8ALTER TYPE old_orderstatus RENAME TO orderstatus;
Add / delete index
1--upgrade
2CREATE INDEX location_gidx ON product USING gist (location);
3CREATE UNIQUE INDEX product_name_supplier_uidx ON product (name, supplier);
4--downgrade
5DROP INDEX location_gidx;
6DROP INDEX product_name_supplier_uidproduct_name_supplier_uidx
Add / delete constraint
1--upgrade - add a unique and foreign key constraint
2ALTER TABLE product ADD CONSTRAINT product_name_supplier_uc UNIQUE USING INDEX product_name_supplier_uidx;
3ALTER TABLE product ADD CONSTRAINT product_supplier_fk FOREIGN KEY (supplier_id) REFERENCES supplier (id) ON DELETE CASCADE;
4--downgrade - remove the unique and foreign key constraint
5ALTER TABLE product DROP CONSTRAINT product_name_supplier_uc;
6ALTER TABLE product DROP CONSTRAINT product_supplier_fk;
Create / delete table
1--upgrade
2CREATE TABLE IF NOT EXISTS "product" (
3 "time_created" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
4 "time_updated" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
5 "id" SERIAL NOT NULL PRIMARY KEY,
6 "name" VARCHAR(64) NOT NULL,
7 "supplier_id" INT REFERENCES "supplier" ("id") ON DELETE CASCADE,
8 "location" GEOMETRY(POINT,4326) NOT NULL,
9 "properties" JSONB,
10 "status" orderstatus NOT NULL DEFAULT 'processed',
11 CONSTRAINT "uc_product_name_supplier" UNIQUE ("name", "supplier_id")
12);
13COMMENT ON COLUMN product.status IS 'PROCESSED: PROCESSED\nSHIPPED: SHIPPED\nDELIVERED: DELIVERED\nCANCELLED: CANCELLED';
14--downgrade
15DROP TABLE product;
Misc. Postgres commands
Unnesting an array column
1-- Generate the table for testing
2
3-- CREATE TABLE IF NOT EXISTS "intervals" (
4-- "id" SERIAL NOT NULL PRIMARY KEY,
5-- "cutoff" decimal[] NOT NULL
6-- );
7-- INSERT INTO intervals (id, cutoff) VALUES (1, '{0.0, 1.0, 2.5, 5.0, 10.0}');
8-- INSERT INTO intervals (id, cutoff) VALUES (2, '{0.0, 10.0, 20.0, 50.0, 100.0, 250.0}');
9
10SELECT
11 id AS interval_id,
12 GENERATE_SERIES(1, ARRAY_LENGTH(cutoff, 1)-1) AS range_id,
13 UNNEST(cutoff[1:ARRAY_LENGTH(cutoff, 1)-1]) AS low,
14 UNNEST(cutoff[2:ARRAY_LENGTH(cutoff, 1)]) AS high
15FROM intervals
Looping through list of ids
This is useful to update a table with a foreign key.
As an example below we assume every user has ordered a specific product.
1DO
2$$
3DECLARE
4 f record;
5BEGIN
6 FOR f IN SELECT id FROM public.users
7 LOOP
8 INSERT INTO order (orderstatus, product_id, user_id)
9 VALUE ('PURCHASED', 123456, f.id);
10 END LOOP;
11END;
12$$