Oracle SQL to find tables hierarchy in a schema - sql

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.

Related

How do I move data from one table to another and update references?

I have a table:
select * from users
id | name | company_name
1 | Sam | Sam's Plumbing
2 | Pat | Pat's Bakery
3 | Vic |
I want to move users.company_name to a new companies table, with users.company_id referencing companies.id. Preferably, I'd like to do this in one transaction.
This expresses what I want conceptually, but isn't valid SQL:
BEGIN;
-- 1: add companies
CREATE TABLE companies (
id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
name varchar(255) not null
);
-- 2: add users.company_id -> companies.id
ALTER TABLE users
ADD COLUMN company_id INT
CONSTRAINT users_company_id_fk REFERENCES companies (id);
-- 3: move users.company_name to companies.name; update FK
UPDATE users
SET users.company_id = inserted_companies.id
FROM (
INSERT INTO companies (name)
SELECT company_name FROM users
WHERE company_name IS NOT NULL
-- this isn't valid; RETURNING can't reference users
RETURNING companies.id, users.id AS user_id
) AS inserted_companies;
-- 4: drop users.company_name
ALTER TABLE users
DROP COLUMN company_name;
COMMIT;
Similar questions that don't quite help:
Error : ERROR: table name specified more than once
How do I move a column (with contents) to another table in a Rails migration?
Adding a LEFT JOIN on a INSERT INTO....RETURNING
create table companies
(
id serial primary key ,
company_name text
);
insert into companies (company_name)
select distinct company_name
from users
where company_name is not null;
alter table users add company_id int null;
update users set company_id = companies.id
from companies where companies.company_name = users.company_name;
ALTER TABLE users
DROP COLUMN company_name;
ALTER TABLE users
ADD CONSTRAINT users_company_id_fk foreign key (company_id) REFERENCES companies (id);
DBFiddle demo
Building off #Cetin.Basoz's excellent answer, here's what I ended up with:
-- 1: add companies:
CREATE TABLE companies
(
id serial primary key,
company_name text
);
-- 2: move users.company_name to companies.name
-- using the users.id as the companies.id for the initial import:
INSERT INTO companies (id, company_name)
SELECT id, company_name
FROM users
WHERE company_name IS NOT NULL;
-- 3: update the companies PK index
-- so we don't try to insert duplicate IDs:
SELECT setval('companies_id_seq', (SELECT MAX(id) FROM companies));
-- 4: add references to the newly-inserted companies:
ALTER TABLE users ADD company_id int null;
UPDATE users
SET company_id = users.id
WHERE company_name IS NOT NULL;
-- 5: drop the vestigial users.company_name
ALTER TABLE users DROP COLUMN company_name;
-- 6: add a FK index:
ALTER TABLE users
ADD CONSTRAINT users_company_id_fk
FOREIGN KEY (company_id)
REFERENCES companies (id);
The companies table will have holes in it, but as long as the id space is sufficiently large, I don't see that as a problem. It would have holes if records were deleted.

insert into table from two another tables - oracle

i have 3 tables. Two First tables has data and i want 3rd table insert data from that first two.
TABLE A :
CREATE TABLE z_ostan ( id NUMBER PRIMARY KEY,
name VARCHAR2(30) NOT NULL CHECK (upper(name)=name)
);
TABLE B:
CREATE TABLE z_shahr ( id NUMBER PRIMARY KEY,
name VARCHAR2(30) NOT NULL CHECK (upper(name)=name),
ref_ostan NUMBER,
CONSTRAINT fk_ref_ostan FOREIGN KEY (ref_ostan) REFERENCES z_ostan(id)
);
TABLE C:
CREATE TABLE z_shar2 ( shahr_name VARCHAR2(30),
ostan_name VARCHAR2(30),
payetakht number);
insert data from TABLE A and B into C by this conditions:
shahr_name in TABLE C comes from z_shahr.name in TABLE B
ostan_name in TABLE C comes from z_ostan .name in TABLE A
and payetakht has two mode:
default null else
if ostan_name is 'somthing' then =1
i CANT INSERT BY This Conditions on TABLE C
Looks like a join:
INSERT INTO z_shar2 (shahr_name, ostan_name, payetakht)
SELECT b.name,
a.name,
CASE WHEN a.name = 'somthing' THEN 1 ELSE NULL END payetakht
FROM z_shahr b JOIN z_ostan a ON a.id = b.ref_ostan
As of payetakht column's value: I initially thought that you, actually, meant when a.name is not null but that can't be as name column is declared as not null, so ... that's probably really (misspelled) somthing.

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;

Database inheritance performance optimization

Say I have a MySQL DB inheritance design as follow :
CREATE TABLE parent
(
id INT PRIMARY KEY AUTO_INCREMENT,
type ENUM('A', 'B')
);
CREATE TABLE child_a
(
parent_id INT PRIMARY KEY,
a_val TEXT
-- FK to parent table...
);
CREATE TABLE child_b
(
parent_id INT PRIMARY KEY,
b_val TEXT,
-- FK to parent table...
);
Then using the following query :
SELECT * FROM parent p
JOIN child_a a ON p.type = 'A' AND a.parent_id = p.id
JOIN child_b b ON p.type = 'B' AND b.parent_id = p.id;
My question is: Does adding the type column check as a first condition in the JOIN going to stop the DB from actually going through every row of the child_a table if the parent.type is 'B'?

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)