SQL constraints and ON DELETE - sql

Given an simple example of foreign key constraints:
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
CREATE TABLE orders (
order_id integer PRIMARY KEY,
shipping_address text,
...
);
CREATE TABLE order_items (
product_no integer REFERENCES products ON DELETE CASCADE,
order_id integer REFERENCES orders ON DELETE CASCADE,
quantity integer,
PRIMARY KEY (product_no, order_id)
);
When a row is deleted from products or orders, referencing rows from order_items are also deleted.
But - it is possible to have the database detect orphaned rows from products or orders (rows for which there is no referencing order_item) and delete them? I know I could set up a query to do it easily enough, but in a larger more complex schema, that could be a lot of queries. I'm wondering if there's a similar mechanism to ON DELETE CASCADE?

Here is an example without reference counts:
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE one
( id SERIAL NOT NULL PRIMARY KEY
, name VARCHAR
);
CREATE TABLE two
( id SERIAL NOT NULL PRIMARY KEY
, name VARCHAR
);
CREATE TABLE onetwo
( one_id INTEGER REFERENCES one(id)
, two_id INTEGER REFERENCES two(id)
, PRIMARY KEY(one_id, two_id)
);
CREATE INDEX onetwo_rev ON onetwo (two_id,one_id);
-- Populate the tables.
INSERT INTO one(name) select 'One_name_' || gs::text FROM generate_series(1,5) gs ;
INSERT INTO two(name) select 'Two_name_' || gs::text FROM generate_series(1,5) gs ;
INSERT INTO onetwo (one_id, two_id)
SELECT o.id, t.id
FROM one o
JOIN two t ON 1=1
;
-- Remove some random associations between one&two,
-- and remove any unreferenced records from one and two
DELETE FROM onetwo WHERE random() < 0.7;
DELETE FROM one dd WHERE NOT EXISTS(SELECT * FROM onetwo ot WHERE ot.one_id=dd.id) ;
DELETE FROM two dd WHERE NOT EXISTS(SELECT * FROM onetwo ot WHERE ot.two_id=dd.id) ;
SELECT * FROM onetwo;
SELECT * FROM one;
SELECT * FROM two;
CREATE OR REPLACE FUNCTION check_the_deletes() RETURNS TRIGGER AS $meat$
BEGIN
DELETE FROM one dd
WHERE dd.id=OLD.one_id
AND NOT EXISTS(SELECT * FROM onetwo ot WHERE ot.one_id=dd.id)
;
DELETE FROM two dd
WHERE dd.id=OLD.two_id
AND NOT EXISTS(SELECT * FROM onetwo ot WHERE ot.two_id=dd.id)
;
RETURN NEW;
END; $meat$
LANGUAGE plpgsql;
CREATE TRIGGER update_last_sale
AFTER DELETE ON onetwo
FOR EACH ROW
EXECUTE PROCEDURE check_the_deletes()
;
SELECT o.name AS name1
, t.name AS name2
FROM onetwo ot
JOIN one o ON o.id = ot.one_id
JOIN two t ON t.id = ot.two_id
;
-- Delete some random associations
-- the trigger should also remove any unreferenced rows
-- from one and two tables.
DELETE FROM onetwo WHERE random() < 0.4;
SELECT o.name AS name1
, t.name AS name2
FROM onetwo ot
JOIN one o ON o.id = ot.one_id
JOIN two t ON t.id = ot.two_id
;
SELECT * FROM one;
SELECT * FROM two;

Related

optmize delete based on "count" of elements in table with foreign key

I has two tables, "tracks" (header of track and track_points - points in track).
Schema:
CREATE TABLE tracks(
id INTEGER PRIMARY KEY ASC,
start_time TEXT NOT NULL
);
CREATE TABLE track_points (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data BLOB,
track_id INTEGER NOT NULL,
FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE
);
CREATE INDEX track_id_idx ON track_points (track_id);
CREATE INDEX start_time_idx ON tracks (start_time);
And I want delete all "tracks" that has 0 or 1 point.
Note if 0 points in tracks, then it has no rows in "track_points".
I write such query:
DELETE FROM tracks WHERE tracks.id IN
(SELECT track_id FROM
(SELECT tracks.id as track_id, COUNT(track_points.id) as track_len FROM tracks
LEFT JOIN track_points ON tracks.id=track_points.track_id GROUP BY tracks.id)
WHERE track_len<=1)
it seems to work, but I wonder is it possible to optmize such query?
I mean time of work (now 10 seconds on big table on my machine).
Or may be simplification of this SQL code is possible (with preservance of work time of course)?
You can simplify your code by removing 1 level of your subqueries, because you can achieve the same with a HAVING clause instead of an outer WHERE clause:
DELETE FROM tracks
WHERE id IN (
SELECT t.id
FROM tracks t LEFT JOIN track_points p
ON t.id = p.track_id
GROUP BY t.id
HAVING COUNT(p.id) <= 1
);
The above code may not make any difference, but it's simpler.
The same logic could also be applied by using EXCEPT:
DELETE FROM tracks
WHERE id IN (
SELECT id FROM tracks
EXCEPT
SELECT track_id
FROM track_points
GROUP BY track_id
HAVING COUNT(*) > 1
);
What you can try is a query that does not involve the join of the 2 tables.
Aggregate only in track_points and get the track_ids with 2 or more occurrences. Then delete all the rows from tracks with ids that are not included in the result of the previous query:
DELETE FROM tracks
WHERE id NOT IN (
SELECT track_id
FROM track_points
GROUP BY track_id
HAVING COUNT(*) > 1
);

Counting Occurrences from One Table and Inserting into Another but Getting an Error

I am trying to count the amount of times each school shows up in a set of records and record that value in a new table with its corresponding school name and ID.
The tables being used are similar to the following:
Table 1-> school_probs
school_code (pk, bigint)
school (text)
probability
1
school1
Irrelevant info
2
school2
ii
3
school3
ii
Table2-> simulated_records
record_id (pk, bigint)
school (text)
grade
1
school1
ii
2
school2
ii
3
school1
ii
4
school3
ii
I'm expecting to get an output like
school_code (fk, bigint)
school (text)
schoolCount (integer)
1
school1
2
2
school2
1
3
school3
1
and I was able to achieve this with the following code:
SELECT COUNT (simulated_records.school) AS schoolCount, school_probs.school_code, school_probs.school
FROM simulated_records, school_probs WHERE school_probs.school = simulated_records.school
GROUP BY simulated_records.school, school_probs.school_code, school_probs.school;
However, I need the result to be saved in a table. But when I try
CREATE TABLE studentCount (
studentNum integer, school_code bigint, school text,
CONSTRAINT fk_sC FOREIGN KEY (school_code) REFERNCES school_probs (school_code)
)
SELECT COUNT (simulated_records.school) AS schoolCount, school_probs.school_code, school_probs.school
FROM simulated_records, school_probs WHERE school_probs.school = simulated_records.school
GROUP BY simulated_records.school, school_probs.school_code, school_probs.school;
I get "ERROR: syntax error at or near "SELECT" LINE 5: SELECT COUNT (simulated_records.school) AS schoolCount, . . . SQL state: 42601 "
Line 5 reads:
SELECT COUNT (simulated_records.school) AS schoolCount, school_probs.school_code, school_probs.school
Can anyone point me in the right direction? I plan on creating a function out of this.
The code to create the tables:
DROP TABLE IF EXISTS school_probs;
CREATE TABLE school_probs
(
school_code bigint NOT NULL PRIMARY KEY,
school text NOT NULL,
probs numeric[] NOT NULL
);
INSERT INTO school_probs VALUES
(1,'school1','{0.05,0.08,0.18,0.3,0.11,0.28}'),
(2,'school2','{0.06,0.1,0.295,0.36,0.12,0.065}'),
(3,'school3','{0.05,0.11,0.35,0.32,0.12,0.05}');
DROP TABLE IF EXISTS simulated_records;
CREATE TABLE simulated_records
(
record_id bigint NOT NULL PRIMARY KEY,
school text NOT NULL,
grade text NOT NULL
);
INSERT INTO simulated_records VALUES
(1,'school1','-'),
(2,'school2','-'),
(3,'school1','-'),
(4, 'school3', '-');
Look up the JOIN syntax and don't use , in the FROM clause. Table aliases could also help.
And the syntax to create a table from a query is CREATE TABLE <table name> AS SELECT .... There are no column or constraint definitions. You can use explicit casts in the query to determine column types. Constraint definitions have to be added later with ALTER TABLE.
CREATE TABLE studentcount
AS
SELECT count(sr.school)::integer studentnum,
sp.school_code::bigint,
sp.school::text
FROM simulated_records sr
INNER JOIN school_probs sp
ON sp.school = sr.school
GROUP BY sp.school,
sp.school_code;
ALTER TABLE studentcount
ADD CONSTRAINT fk_sc
FOREIGN KEY (school_code)
REFERENCES school_probs
(school_code);
Alternatively you can first issue a "normal" CREATE TABLE with column and constraint definitions and then insert the rows from the query.
CREATE TABLE studentcount
(studentnum integer,
school_code bigint,
school text,
CONSTRAINT fk_sc
FOREIGN KEY (school_code)
REFERENCES school_probs
(school_code));
INSERT INTO studentcount
(studentnum,
school_code,
school)
SELECT count(sr.school),
sp.school_code,
sp.school
FROM simulated_records sr
INNER JOIN school_probs sp
ON sp.school = sr.school
GROUP BY sp.school,
sp.school_code;
But be aware that you're creating data redundancy either way. That can lead to inconsistencies and should be avoided. If you don't just need that temporarily but later again with then current values consider a view.
CREATE VIEW studentcount
AS
SELECT count(sr.school)::integer studentnum,
sp.school_code::bigint,
sp.school::text
FROM simulated_records sr
INNER JOIN school_probs sp
ON sp.school = sr.school
GROUP BY sp.school,
sp.school_code;

Role basesd access control recursive SQL query

I made the following role based access database schema which is able to hold roles, operations and types. A role can perform a specific operation on a type. Connections to users or types is not important here because this will be application specific. Every of these three tables can have as many parents as they want.
At the moment I'm struggling with a query which outputs every possible combination from the role_operation_type table.
Every role should inherit every permission on a types from the ancestors which can be more than one. In my opinion I need three nested recursive with queries for that or is there any faster way to achieve that?
My intention is to put that query in a view and select the needed values when a user requests an operation on a type.
Here is the database schema:
CREATE TABLE IF NOT EXISTS `role` (
`id` INTEGER PRIMARY KEY,
`name` VARCHAR NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS `role_role` (
`role_id` INTEGER NOT NULL REFERENCES `role`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
`parent_role_id` INTEGER NOT NULL REFERENCES `role`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT role_uq UNIQUE (`role_id`, `parent_role_id`),
CONSTRAINT role_chk CHECK(`role_id` != `parent_role_id`)
);
CREATE TABLE IF NOT EXISTS `operation` (
`id` INTEGER PRIMARY KEY,
`name` VARCHAR NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS `operation_operation` (
`operation_id` INTEGER NOT NULL REFERENCES `operation`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
`parent_operation_id` INTEGER NOT NULL REFERENCES `operation`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT operation_uq UNIQUE (`operation_id`, `parent_operation_id`),
CONSTRAINT operation_chk CHECK(`operation_id` != `parent_operation_id`)
);
CREATE TABLE IF NOT EXISTS `type` (
`id` INTEGER PRIMARY KEY,
`name` VARCHAR NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS `type_type` (
`type_id` INTEGER NOT NULL REFERENCES `type`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
`parent_type_id` INTEGER NOT NULL REFERENCES `type`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT type_uq UNIQUE (`type_id`, `parent_type_id`),
CONSTRAINT type_chk CHECK(`type_id` != `parent_type_id`)
);
CREATE TABLE IF NOT EXISTS `role_operation_type` (
`role_id` INTEGER NOT NULL REFERENCES `role`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`operation_id` INTEGER NOT NULL REFERENCES `operation`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`type_id` INTEGER NOT NULL REFERENCES `type`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT role_id_operation_id_type_id_uq UNIQUE (`role_id`, `operation_id`, `type_id`)
);
CREATE VIEW IF NOT EXISTS role_role_recursive_view AS
WITH RECURSIVE p(role_id, r, parent_role_id) AS (
SELECT ROLE.id, ROLE.id, role_role.parent_role_id
FROM ROLE
INNER JOIN role_role ON ROLE.id = role_role.role_id
UNION
SELECT p.r, p.role_id, role_role.parent_role_id
FROM p
INNER JOIN role_role ON p.parent_role_id = role_role.role_id
WHERE p.r != role_role.parent_role_id
)
SELECT p.role_id, p.parent_role_id FROM p ORDER BY role_id;
CREATE VIEW IF NOT EXISTS operation_operation_recursive_view AS
WITH RECURSIVE o(operation_id, o, parent_operation_id) AS (
SELECT operation.id, operation.id, operation_operation.parent_operation_id
FROM operation
INNER JOIN operation_operation ON operation.id = operation_operation.operation_id
UNION
SELECT o.o, o.operation_id, operation_operation.parent_operation_id
FROM o
INNER JOIN operation_operation ON o.parent_operation_id = operation_operation.operation_id
WHERE o.o != operation_operation.parent_operation_id
)
SELECT o.operation_id, o.parent_operation_id FROM o ORDER BY operation_id;
CREATE VIEW IF NOT EXISTS type_type_recursive_view AS
WITH RECURSIVE t(type_id, t, parent_type_id) AS (
SELECT TYPE.id, TYPE.id, type_type.parent_type_id
FROM TYPE
INNER JOIN type_type ON TYPE.id = type_type.type_id
UNION
SELECT t.t, t.type_id, type_type.parent_type_id
FROM t
INNER JOIN type_type ON t.parent_type_id = type_type.type_id
WHERE t.t != type_type.parent_type_id
)
SELECT t.type_id, t.parent_type_id FROM t ORDER BY type_id;
Now I will answer to my own question with a solution which works quite well. This is a recursive query and generates every possible combination. This is a recursive query which might be slow when the inheritance level becomes deeper.
CREATE VIEW IF NOT EXISTS role_operation_type_recursive_view AS
WITH RECURSIVE T(role_id, operation_id, type_id) AS (
WITH RECURSIVE O(role_id, operation_id, type_id) AS (
WITH RECURSIVE R(role_id, operation_id, type_id) AS (
SELECT role_id, operation_id,type_id
FROM role_operation_type
UNION
SELECT role_role_recursive_view.role_id, R.operation_id, R.type_id
FROM R
INNER JOIN role_role_recursive_view ON R.role_id = role_role_recursive_view.parent_role_id
)
SELECT * FROM R
UNION
SELECT O.role_id,operation_operation_recursive_view.parent_operation_id ,O.type_id
FROM O
INNER JOIN operation_operation_recursive_view ON O.operation_id = operation_operation_recursive_view.operation_id
)
SELECT * FROM O
UNION
SELECT T.role_id, T.operation_id, type_type_recursive_view.type_id
FROM T
INNER JOIN type_type_recursive_view ON T.type_id = type_type_recursive_view.parent_type_id
)
SELECT * FROM T;

Oracle SQL to find tables hierarchy in a schema

I have a need to import some data for a given input from multiple table's and schema's based on a key.
For-Ex: I have ACCOUNT_ID COLUMN which is common to multiple tables (tables are interdependent with FK). I want to import data for a specific account from all the tables from multiple schema's and generate a SQL script.
But the challenge is, I need to identify parent table's first and then their child table's (tied with FK) in the order so that when I run the script, it shouldn't error out with integrity violation errors.
One way is to disable all the constraints, generate the script, run and then enable the constraints.
But I am trying to find if there's a better way of doing this. And enabling/disabling the constraints may not be a good solution for me.
Appreciate any inputs on this issue.
You say that your tables create a hierarchy based on foreign keys, so that for example T1 references T2, and T2 references T3 and so on.
In this case you need a hierarchical query.
First create a view that shows references between tables:
select c.table_name, uc.table_name as referenced_table_name
from user_constraints c
join USER_CONS_columns uc ON c.r_constraint_name = uc.constraint_name
where c.constraint_type = 'R'
For example in this case:
CREATE TABLE ttt1(
ACCOUNT_ID int primary key,
somecolumn varchar2(100)
);
CREATE TABLE ttt21(
id int primary key,
ACCOUNT_ID int referencing ttt1( ACCOUNT_ID ),
somecolumn varchar2(100)
);
CREATE TABLE ttt22(
id int primary key,
ACCOUNT_ID int referencing ttt1( ACCOUNT_ID ),
somecolumn varchar2(100)
);
CREATE TABLE ttt211(
id int primary key,
ACCOUNT_ID int referencing ttt21( id ),
somecolumn varchar2(100)
);
CREATE TABLE ttt2111(
id int primary key,
ACCOUNT_ID int referencing ttt211( id ),
somecolumn varchar2(100)
);
CREATE TABLE ttt2112(
id int primary key,
ACCOUNT_ID int referencing ttt211( id ),
somecolumn varchar2(100)
);
the view gives:
TABLE_NAME REFERENCED_TABLE_NAME
---------- -------------------------
TTT22 TTT1
TTT211 TTT21
TTT21 TTT1
TTT2111 TTT211
TTT2112 TTT211
And now, with the help of this view, you can create a hierarchical query:
WITH my_view AS(
select c.table_name, uc.table_name as referenced_table_name
from user_constraints c
join USER_CONS_columns uc ON c.r_constraint_name = uc.constraint_name
where c.constraint_type = 'R'
) SELECT level, m.referenced_table_name
FROM my_view m
START WITH referenced_table_name not in (select table_name from my_view )
CONNECT BY prior table_name = referenced_table_name;
which gives the following output:
LEVEL REFERENCED_TABLE_NAME
---------- -------------------------
1 TTT1
2 TTT21
3 TTT211
3 TTT211
1 TTT1
A LEVEL column gives an order in which tables must be imported - first you need to process all tables at level 1, then at level 2 and so on and so on.

Including a set of rows in a view column

Design:
A main table where each entry in it can have zero of more of a set of options “checked”. It seems to me that it would be easier to maintain (adding/removing options) if the options were part of a separate table and a mapping was made between the main table and an options table.
Goal:
A view that contains the information from the main table, as well as all options to which that row has been mapped. However the latter information exists in the view, it should be possible to extract the option’s ID and its description easily.
The implementation below is specific to PostgreSQL, but any paradigm that works across databases is of interest.
The select statement that does what I want is:
WITH tmp AS (
SELECT
tmap.MainID AS MainID,
array_agg(temp_options) AS options
FROM tstng.tmap
INNER JOIN (SELECT id, description FROM tstng.toptions ORDER BY description ASC) AS temp_options
ON tmap.OptionID = temp_options.id
GROUP BY tmap.MainID
)
SELECT tmain.id, tmain.contentcolumns, tmp.options
FROM tstng.tmain
INNER JOIN tmp
ON tmain.id = tmp.MainID;
However, attempting to create a view from this select statement generates an error:
column "options" has pseudo-type record[]
The solution that I’ve found is to cast the array of options (record[]) to a text array (text[][]); however, I’m interested in knowing if there is a better solution out there.
For reference, the create instruction:
CREATE OR REPLACE VIEW tstng.vsolution AS
WITH tmp AS (
SELECT
tmap.MainID AS MainID,
array_agg(temp_options) AS options
FROM tstng.tmap
INNER JOIN (SELECT id, description FROM tstng.toptions ORDER BY description ASC) AS temp_options
ON tmap.OptionID = temp_options.id
GROUP BY tmap.MainID
)
SELECT tmain.id, tmain.contentcolumns, CAST(tmp.options AS text[][])
FROM tstng.tmain
INNER JOIN tmp
ON tmain.id = tmp.MainID;
Finally, the DDL in case my description has been unclear:
CREATE TABLE tstng.tmap (
mainid INTEGER NOT NULL,
optionid INTEGER NOT NULL
);
CREATE TABLE tstng.toptions (
id INTEGER NOT NULL,
description text NOT NULL,
unwanted_column text
);
CREATE TABLE tstng.tmain (
id INTEGER NOT NULL,
contentcolumns text
);
ALTER TABLE tstng.tmain ADD CONSTRAINT main_pkey PRIMARY KEY (id);
ALTER TABLE tstng.toptions ADD CONSTRAINT toptions_pkey PRIMARY KEY (id);
ALTER TABLE tstng.tmap ADD CONSTRAINT tmap_pkey PRIMARY KEY (mainid, optionid);
ALTER TABLE tstng.tmap ADD CONSTRAINT tmap_optionid_fkey FOREIGN KEY (optionid)
REFERENCES tstng.toptions (id);
ALTER TABLE tstng.tmap ADD CONSTRAINT tmap_mainid_fkey FOREIGN KEY (mainid)
REFERENCES tstng.tmain (id);
You could create composite type e.g. temp_options_type with:
DROP TYPE IF EXISTS temp_options_type;
CREATE TYPE temp_options_type AS (id integer, description text);
After that just cast temp_options to that type within array_agg, so it returns temp_options_type[] instead of record[]:
DROP VIEW IF EXISTS tstng.vsolution;
CREATE OR REPLACE VIEW tstng.vsolution AS
WITH tmp AS
(
SELECT
tmap.MainID AS MainID,
array_agg(CAST(temp_options AS temp_options_type)) AS options
FROM
tstng.tmap INNER JOIN
(
SELECT id, description
FROM tstng.toptions
ORDER BY description
) temp_options
ON tmap.OptionID = temp_options.id
GROUP BY tmap.MainID
)
SELECT tmain.id, tmain.contentcolumns, tmp.options
FROM tstng.tmain
INNER JOIN tmp ON tmain.id = tmp.MainID;
Example result:
TABLE tstng.vsolution;
id | contentcolumns | options
----+----------------+-----------------------
1 | aaa | {"(1,xxx)","(2,yyy)"}
2 | bbb | {"(3,zzz)"}
3 | ccc | {"(1,xxx)"}
(3 rows)