Need to generate CREATE TABLE scripts and INSERT scripts in order - sql

CREATE TABLE a (
a_id NUMBER(10),
city VARCHAR2(255),
CONSTRAINT pk_a PRIMARY KEY ( a_id )
);
CREATE TABLE b (
b_id NUMBER(10),
a_id NUMBER(10),
city VARCHAR2(255),
CONSTRAINT pk_b PRIMARY KEY ( b_id ),
CONSTRAINT fk_b_a FOREIGN KEY ( a_id )
REFERENCES a ( a_id )
);
INSERT INTO a VALUES(1,'Mumbai');
INSERT INTO a VALUES(2,'Pune');
INSERT INTO b VALUES(1,1,'Mumbai');
INSERT INTO b VALUES(2,2,'Pune');
COMMIT;
I need to generate a create table ddl scripts in order of parent-child. Suppose, I have two tables A and B then script should give me ddl for table A and then for B.
From the below query I can get the parent child tables but how to generate ddl out of it.
SELECT p.table_name PARENT_TABLE, c.table_name CHILD_TABLE
FROM dba_constraints p, dba_constraints c
WHERE (p.constraint_type = 'P' OR p.constraint_type = 'U')
AND c.constraint_type = 'R'
AND p.constraint_name = c.r_constraint_name
AND p.table_name = UPPER('A')
and p.owner='TAM';
And same for INSERT DML scripts. I need to have the DML scripts in the order of parent-child.
I am stuck in generating the scripts.

I wouldn't bother.
I presume your intention is to port current schema to another schema/database.
if you already have such a script - that creates tables in appropriate order, as well as inserts rows into them - reuse it
you could help yourself if you first created tables (without foreign key constraints), then inserted rows, then created foreign key constraints (using alter table add constraint ...)
however, I'd suggest you to use Data Pump instead (or maybe even the original EXP and IMP utilities) - export will export the whole schema (if that's what you're doing) into a .DMP file; import would then import it into another schema, taking care about foreign key constraints (basically, it would do as I wrote before - apply constraints at the end of import)
it means that you don't have to take care about what comes first - Oracle will do everything for you

Related

Performance of ON DELETE CASCADE in PostgresSQL

I have an issue related to performance of ON DELETE CASCADE. I'm trying to understand why it takes so long. For this topic purposes I simplified real case to schema presented below:
CREATE TABLE IF NOT EXISTS public.items
(
id uuid NOT NULL,
name text COLLATE pg_catalog."default",
CONSTRAINT items_pk PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public.links
(
parent uuid,
child uuid,
CONSTRAINT links_parent_fk FOREIGN KEY (parent)
REFERENCES public.items (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT links_child_fk FOREIGN KEY (child)
REFERENCES public.items (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS parent_idx
ON public.links USING btree
(parent ASC NULLS LAST);
CREATE INDEX IF NOT EXISTS child_idx
ON public.links USING btree
(child ASC NULLS LAST);
CREATE EXTENSION "uuid-ossp";
and data can be generated with:
INSERT INTO public.items
SELECT uuid_generate_v4 (), 'item_' || i
FROM generate_series(1, 134001) AS i;
INSERT INTO links
SELECT (SELECT id FROM public.items WHERE name='item_1'), id FROM public.items;
Briefly, data base contains two tables. Table items contains a list of items (identifier and name column) and table links which defines relations between items (parent <-> child). In presented case all items (children) belongs to item named 'item_1' (parent).
I call a query in order to delete all children assigned to parent:
BEGIN;
EXPLAIN ANALYZE DELETE FROM public.items where id in (SELECT child FROM public.links WHERE parent = (SELECT id FROM public.items WHERE name='item_1'));
ROLLBACK;
From execution plan we can read among others:
"Trigger for constraint links_parent_fk: time=10451.471 calls=134001"
"Trigger for constraint links_child_fk: time=2962.035 calls=134001"
The question is why trigger for constraint links_parent_fk consumes a lot time?
I performed some attempts with exchanging data between columns in links table. After that trigger for links_child_fk consumed ~10 s and trigger for links_parent_fk took ~3 s. I'm curious why there is such difference between execution of this delete cascades?
PostgreSQL version: 12.4 and 13.9.

Create constraint for control insert in table

There are two tables - orders and a list of services. In the first there is a bool field that the order is approved, if it is true then you can’t insert / delete values in the second table. With the UPDATE of the first table and the DELETE of the second, it is clear.
INSERT make as
INSERT INTO b (a_id, b_value)
SELECT *
FROM (VALUES (1, 'AA1-BB1'),(1, 'AA1-BB2'),(1, 'AA1-BB3')) va
WHERE (SELECT NOT confirm FROM a WHERE a_id = 2);
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=7b0086967c1c38b0c80ca5624ebe92e9
How to forbid to insert without triggers and stored procedures? Is it possible to compose somehow complex constraint or a foreign key for checking conditions at the DBMS level?
The most recent version of Postgres supports generated columns. So, you can do:
alter table b add confirm boolean generated always as (false) stored;
Then create a unique key in a:
alter table a add constraint unq_a_confirm_id unique (confirm, id);
And finally the foreign key relationship:
alter table b add constraint fk_b_a_id_confirm
foreign key (confirm, a_id) references a(confirm, id);
Now, only confirmed = false ids can be used. Note that this will prevent updates to a that would invalidate the foreign key constraint.

Is it safe to drop and then create the foreign key constraints inside a transaction?

I have a table A that references a table B. Table B needs to be populated with updated data from an external source and for efficiency I use TRUNCATE followed by a COPY. This is done even when the application is live.
To further improve efficiency, as suggested in the documentation, I want to drop and then recreate the foreign keys.
However I have some doubts.
If I drop the FKs, COPY and then recreate FKs inside the same transaction, can I be sure that the constraint is preserved even on data inserted in table A during the transaction? I ask this because in theory a transaction is atomic, but in the docs, about the temporary removal of FKs say:
there is a trade-off between data load speed and loss of error checking while the constraint is missing.
If there's a chance that a wrong reference is inserted in the meantime, what happens when you try to recreate the FK constraints?
TRUNCATE is not allowed on any table referenced by a foreign key, unless you use TRUNCATE CASCADE, which will also truncate the referencing tables. The DEFERRABLE status of the constraint does not affect this. I don't think there is any way around this; you will need to drop the constraint.
However, there is no risk of an integrity violation in doing so. ALTER TABLE ... ADD CONSTRAINT locks the table in question (as does TRUNCATE), so your import process is guaranteed to have exclusive access to the table for the duration of its transaction. Any attempts at concurrent inserts will simply hang until the import has committed, and by the time they are allowed to proceeed, the constraint will be back in place.
You can make the foreign key constraint deferrable (initially deferred). That way it will be checked just once at the end of the transaction.
ALTER TABLE
xxx
ADD CONSTRAINT
xxx_yyy_id_fk FOREIGN KEY (yyy_id)
REFERENCES
yyy
DEFERRABLE INITIALLY DEFERRED;
In all the cases, transactions are fully atomic in PostgreSQL (not only in theory), including DDL statements (such as CREATE/DROP constraint), so even if you drop a foreign key, then insert data, then create the foreign key and do everything in one transaction, then you are safe - if the recreation of the foreign key constraint fails, then the inserted data will also be dismissed.
Still, it is better to switch to deferred foreign keys, rather than dropping and then creating them.
Analytic answer: measure the number of new/same/updated/deleted records.
There are four cases:
The key in the B table is not present in the b_import: delete
The key in the b_import is not present on the old B: insert
The key is present in both old B and new B, but the contents are the same: ignore
The keys are the same, but the attribete values differ: Update
-- some test data for `A`, `B` and `B_import`:
CREATE TABLE b
( id INTEGER NOT NULL PRIMARY KEY
, payload varchar
);
INSERT INTO b(id,payload) SELECT gs, 'bb_' || gs::varchar
FROM generate_series(1,20) gs;
CREATE TABLE b_import
( id INTEGER NOT NULL PRIMARY KEY
, payload varchar
);
INSERT INTO b_import(id,payload) SELECT gs, 'bb_' || gs::varchar
FROM generate_series(10,15) gs;
-- In real life this table will be filled by a `COPY b_import FROM ...`
INSERT INTO b_import(id,payload) SELECT gs, 'b2_' || gs::varchar
FROM generate_series(16,25) gs;
CREATE TABLE a
( id SERIAL NOT NULL PRIMARY KEY
, b_id INTEGER references b(id) ON DELETE SET NULL
, aaaaa varchar
);
INSERT INTO a(b_id,aaaaa)
SELECT gs,'aaaaa_' || gs::text FROM generate_series(1,20) gs;
CREATE INDEX ON a(b_id); -- index supporting the FK
-- show it
SELECT a.id, a.aaaaa
,b.id, b.payload AS oldpayload
FROM a
FULL JOIN b ON a.b_id=b.id
ORDER BY a.id;
-- Do the actual I/U/D and report the numbers of affected rows
-- EXPLAIN
WITH ins AS ( -- INSERTS
INSERT INTO b(id, payload)
SELECT b_import.id, b_import.payload
FROM b_import
WHERE NOT EXISTS (
SELECT 1 FROM b
WHERE b.id = b_import.id
)
RETURNING b.id
)
, del AS ( -- DELETES
DELETE FROM b
WHERE NOT EXISTS (
SELECT 2 FROM b_import
WHERE b_import.id = b.id
)
RETURNING b.id
)
, upd AS ( -- UPDATES
UPDATE b
SET payload=b_import.payload
FROM b_import
WHERE b_import.id = b.id
AND b_import.payload IS DISTINCT FROM b.payload -- exclude idempotent updates
-- AND NOT EXISTS ( -- exclude deleted records
-- SELECT 3 FROM del
-- WHERE del.id = b_import.id
-- )
-- AND NOT EXISTS ( -- avoid touching freshly inserted rows
-- SELECT 4 FROM ins
-- WHERE ins.id = b_import.id
-- )
RETURNING b.id
)
SELECT COUNT(*) AS orgb
, (SELECT COUNT(*) FROM b_import) AS newb
, (SELECT COUNT(*) FROM ins) AS ninserted
, (SELECT COUNT(*) FROM del) AS ndeleted
, (SELECT COUNT(*) FROM upd) AS nupdated
FROM b
;
Dropping a constraint and rebuilding it after the import is expensive: all the records in both A and B are involved.
temporally ignoring the constraint is dangerous: the new B table could miss some rows that are still referenced by A's FK.
ergo: You could end up with a crippled model, and you'd have to rebuild As references (which is basically impossible, without additional information (which would be redundant, BTW))

Creating a Foreign Key based on an index name rather than table(columns)

I have a table with a timestamp field onto which I've created a composite index.
CREATE INDEX "IDX_NAME_A" ON TABLE "A" (a_id, extract(year FROM created_at))
I have another table which stores a year and a_id which I'd like to have a foreign key relation.
I can't seem to find the syntax to do what I want.
ALTER TABLE "B"
ADD FOREIGN KEY(a_id, a_year)
REFERENCES A(a_id, extract(YEAR FROM created_at));
produces:
ERROR: syntax error at or near "("
I've also tried ...
ALTER TABLE "B"
ADD FOREIGN KEY(a_id, a_year)
USING INDEX "IDX_NAME_A";
Any Ideas?
Table A
--------
a_id serial,
created_at timestamp default now()
Table B
-------
b_id serial
a_id integer not null,
a_year date_part('year')
A foreign key constraint cannot reference an index. It has to be a table.
A foreign key constraint cannot reference an expression. It has to point to column name(s) of the referenced table.
And there has to exist a unique index (primary key qualifies, too, implicitly) on the set of referenced columns.
Start by reading the manual about foreign keys here.
The superior design would be to just drop the column b.a_year. It is 100% redundant and can be derived from a.created_at any time.
If you positively need the column (for instance to enforce one row per year for certain criteria in table b), you can achieve your goal like this:
CREATE TABLE a (
a_id serial
,created_at timestamp NOT NULL DEFAULT now()
,a_year NOT NULL DEFAULT extract(year FROM now())::int -- redundant, for fk
,CHECK (a_year = extract(year FROM created_at)::int)
);
CREATE UNIQUE INDEX a_id_a_year_idx ON TABLE a (a_id, a_year); -- needed for fk
CREATE TABLE b (
b_id serial
,a_id integer NOT NULL
,a_year int -- doesn't have to be NOT NULL, but might
,CONSTRAINT id_year FOREIGN KEY (a_id, a_year) REFERENCES a(a_id, a_year)
);
Updated after #Catcall's comment:
The CHECK constraint in combination with the column DEFAULT and NOT NULL clauses enforces your regime.
Alternatively (less simple, but allowing for NULL values) you could maintain the values in a.a_year with a trigger:
CREATE OR REPLACE FUNCTION trg_a_insupbef()
RETURNS trigger AS
$BODY$
BEGIN
NEW.a_year := extract(year FROM NEW.created_at)::int;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER insupbef
BEFORE INSERT OR UPDATE ON a
FOR EACH ROW EXECUTE PROCEDURE trg_a_insupbef();

PostgreSQL delete fails with ON DELETE rule on inherited table

In my PostgreSQL 9.1 database I've defined RULEs that delete rows from child tables whenever a parent table row is deleted. This all worked OK, until I introduced inheritance. If the parent (referencing) table INHERITS from another table and I delete from the base table then the DELETE succeeds, but the RULE doesn't appear to fire at all - the referenced row is not deleted. If I try to delete from the derived table I get an error:
update or delete on table "referenced" violates foreign key constraint "fk_derived_referenced" on table "derived"
There is no other row in the parent table that would violate the foreign key: it's being referenced by the row that's being deleted! How do I fix this?
The following script reproduces the problem:
-- Schema
CREATE TABLE base
(
id serial NOT NULL,
name character varying(100),
CONSTRAINT pk_base PRIMARY KEY (id)
);
CREATE TABLE referenced
(
id serial NOT NULL,
value character varying(100),
CONSTRAINT pk_referenced PRIMARY KEY (id)
);
CREATE TABLE derived
(
referenced_id integer,
CONSTRAINT pk_derived PRIMARY KEY (id),
CONSTRAINT fk_derived_referenced FOREIGN KEY (referenced_id) REFERENCES referenced (id)
)
INHERITS (base);
-- The rule
CREATE OR REPLACE RULE rl_derived_delete_referenced
AS ON DELETE TO derived DO ALSO
DELETE FROM referenced r WHERE r.id = old.referenced_id;
-- Some test data
INSERT INTO referenced (id, value)
VALUES (1, 'referenced 1');
INSERT INTO derived (id, name, referenced_id)
VALUES (2, 'derived 2', 1);
-- Delete from base - deletes the "base" and "derived" rows, but not "referenced"
--DELETE FROM base
--WHERE id = 2;
-- Delete from derived - fails with:
-- update or delete on table "referenced" violates foreign key constraint "fk_derived_referenced" on table "derived"
DELETE FROM derived
WHERE id = 2
As I said in my comment, this seems an unusual way to do things. But you can make it work with a deferred constraint.
CREATE TABLE derived
(
referenced_id integer,
CONSTRAINT pk_derived PRIMARY KEY (id),
CONSTRAINT fk_derived_referenced FOREIGN KEY (referenced_id)
REFERENCES referenced (id) DEFERRABLE INITIALLY DEFERRED
)
INHERITS (base);
The PostgreSQL docs, Rules vs. Triggers, say
Many things that can be done using triggers can also be implemented
using the PostgreSQL rule system. One of the things that cannot be
implemented by rules are some kinds of constraints, especially foreign
keys.
But it's not clear to me that this specific limitation is what you're running into.
Also, you need to check if other records are still referencing the to-be-deleted rows. I added a test derived record#3, which points to the same #1 reference record.
-- The rule
CREATE OR REPLACE RULE rl_derived_delete_referenced
AS ON DELETE TO tmp.derived DO ALSO (
DELETE FROM tmp.referenced re_del
WHERE re_del.id = OLD.referenced_id
AND NOT EXISTS ( SELECT * FROM tmp.derived other
WHERE other.referenced_id = re_del.id
AND other.id <> OLD.id )
;
);
-- Some test data
INSERT INTO tmp.referenced (id, value)
VALUES (1, 'referenced 1');
-- EXPLAIN ANALYZE
INSERT INTO tmp.derived (id, name, referenced_id)
VALUES (2, 'derived 2', 1);
INSERT INTO tmp.derived (id, name, referenced_id)
VALUES (3, 'derived 3', 1);
-- Delete from base - deletes the "base" and "derived" rows, but not "referenced"
--DELETE FROM base
--WHERE id = 2;
-- Delete from derived - fails with:
-- update or delete on table "referenced" violates foreign key constraint "fk_derived_referenced" on table "derived"
EXPLAIN ANALYZE
DELETE FROM tmp.derived
WHERE id = 2
;
SELECT * FROM tmp.base;
SELECT * FROM tmp.derived;
SELECT * FROM tmp.referenced;