Database inheritance performance optimization - sql

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'?

Related

How to join 2 tables and have a list of other table's values nested in the first one in PostgreSQL?

Let's assume that we have these 2 tables: person and car
CREATE TABLE person (
id BIGSERIAL NOT NULL PRIMARY KEY,
name VARCHAR NOT NULL
);
CREATE TABLE car (
id BIGSERIAL NOT NULL PRIMARY KEY,
make VARCHAR NOT NULL,
person_id BIGINT NOT NULL REFERENCES person(id)
);
What I try to do is to find all people, find every car and create an array of objects like this one
[
{
"id": "PERSON_ID",
"name": "PERSON_NAME",
"cars": [
{
"id": "CAR_ID",
"model": "MODEL_NAME",
"person_id": "PERSON_ID"
}
]
}
]
I have tried using the AS alias with a JOIN on person table from car table but it didn't work. Is there a way to do this? Thank you!
You may try the following. See a working fiddle:
Schema (PostgreSQL v13)
CREATE TABLE person (
id BIGSERIAL NOT NULL PRIMARY KEY,
name VARCHAR NOT NULL
);
CREATE TABLE car (
id BIGSERIAL NOT NULL PRIMARY KEY,
make VARCHAR NOT NULL,
person_id BIGINT NOT NULL REFERENCES person(id)
);
insert into person(name) values ('tom'),('harry');
insert into car (id,make,person_id) values (1,'ford',1),(2,'audi',1),(3,'nissan',2);
Query #1
SELECT
p.id,
p.name,
array_agg(row_to_json(c)) cars
FROM
person p
INNER JOIN
(SELECT id, make model, person_id FROM car) c ON p.id = c.person_id
GROUP BY
p.id,
p.name;
id
name
cars
1
tom
[{"id":1,"model":"ford","person_id":1},{"id":2,"model":"audi","person_id":1}]
2
harry
[{"id":3,"model":"nissan","person_id":2}]
View on DB Fiddle
If you want the result set as an array in Postgres, you can use:
select p.*, array_agg(c)
from person p join
car c
on c.person_id = p.id
group by p.id;
You can do something similar if you want JSON returned as well.

Postgres many-to-many relation: finding rows all having relation to a set of rows from related table

Consider three tables, let's call them groups, subgroups, another_groups and table subgroups_another_groups that is specifying many-to-many relation between subgroups and another_groups. subgroups and groups are in one-to-many relation, so subgroups has foreign key group_id.
How is it possible to select another_groups that all of subgroups within a group have relation to?
I assume that you are speaking of such a setup:
CREATE TABLE groups (
id integer PRIMARY KEY
);
CREATE TABLE subgroups (
id integer PRIMARY KEY,
group_id integer REFERENCES groups NOT NULL
);
CREATE INDEX ON subgroups(group_id);
CREATE TABLE another_groups (
id integer PRIMARY KEY
);
CREATE TABLE subgroups_another_groups (
subgroup_id integer REFERENCES subgroups NOT NULL,
another_groups_id integer REFERENCES another_groups NOT NULL,
PRIMARY KEY(subgroup_id, another_groups_id)
);
CREATE INDEX ON subgroups_another_groups(another_groups_id);
Then you want to know all another_groups that are connected to a groups via the other two tables except the ones where there is a subgroup that does not have a connection to this another_groups, right?
In SQL, that would read:
SELECT DISTINCT g.id, a.id
FROM another_groups a
JOIN subgroups_another_groups sag ON a.id = sag.another_groups_id
JOIN subgroups s ON sag.subgroup_id = s.id
JOIN groups g ON s.group_id = g.id
WHERE NOT EXISTS
(SELECT 1 FROM subgroups s1
WHERE s1.group_id = g.id
AND NOT EXISTS
(SELECT 1 FROM subgroups_another_groups sag1
WHERE sag1.subgroup_id = s1.id
AND sag1.another_groups_id = a.id
)
);

postgres: (sub)select and combine optional content into an array

i have the following table structure:
Location----- * Media ----1 Attribute --------* AttributeTranslation
Each Location has n mediaitems attached, containing one optional attribute (text) and n associated translationa for that attribute.
I need to select this data into an array, so that i get for each location the associated medialist for each language.
what i currently do and what i get:
SELECT m.location_id, t.language_id,
array_agg_mult(
ARRAY[ARRAY[m.sortorder::text, m.filename, t.name]] ORDER BY m.sortorder
) as medialist
FROM Media m
LEFT JOIN ATTRIBUTE a ON a.id = m.attribute_id
LEFT JOIN AttributeTranslation t ON a.id = t.attribute_id
WHERE m.location_id = ?
GROUP BY m.location_id, t.language_id
This gives me following result for the given scenario: the current location has 4 images attached, only the first image has an associated attribute containing two translations:
Location_ID Language_ID MEDIALIST
AT_014 1 {{1,'location_image1.jpg','attribute german'}}
AT_014 2 {{1,'location_image1.jpg','attribute english'}}
AT_014 {{2,'location_image2.jpg',null},{3,'location_image3.jpg',null},{4,'location_image4.jpg',null}}
but what i need instead is this:
Location_ID Language_ID MEDIALIST
AT_014 1 {{1,'location_image1.jpg','attribute german'},{2,'location_image2.jpg',null},{3,'location_image3.jpg',null},{4,'location_image4.jpg',null}}
AT_014 2 {{1,'location_image1.jpg','attribute english'},{2,'location_image2.jpg',null},{3,'location_image3.jpg',null},{4,'location_image4.jpg',null}}
those 3 columns are part of a view, so that i can do later:
select * from locationview where location_id = ? and language_id = ?
how can i achieve the desired result here? thanks in advance!
Simplified Table Definitions:
CREATE TABLE LOCATION (
location_id numeric(20) primary key,
description text
);
CREATE TABLE MEDIA (
media_id numeric(20) primary key,
fileName text,
sortorder smallint,
location_id numeric(20) references LOCATION(location_id),
attribute_id numeric(20) references ATTRIBUTE(attribute_id)
);
CREATE TABLE ATTRIBUTE (
attribute_id numeric(20) primary key,
attributetype varchar(100),
);
CREATE TABLE ATTRIBUTETRANSLATION (
translation_id numeric(20),
language_id smallint,
name text,
description text,
attribute_id numeric(20) references ATTRIBUTE(attribute_id)
);
ALTER TABLE ATTRIBUTETRANSLATION add constraint AT_ID primary key(translation_id, language_id)
I am not sure I fully understand your question, but here's an attempt. You could take the output of your query, and match each row that has a language_id with the corresponding rows where language_id is NULL, so that you can then concatenate the medialist arrays. Here's a way to do that by creating an alias of your query with a CTE:
WITH t AS (
SELECT m.location_id, t.language_id,
array_agg(
ARRAY[ARRAY[m.sortorder::text, m.filename, t.name]] ORDER BY m.sortorder
) as medialist
FROM Media m
LEFT JOIN ATTRIBUTE a ON a.attribute_id = m.attribute_id
LEFT JOIN AttributeTranslation t ON a.attribute_id = t.attribute_id
WHERE m.location_id = ?
GROUP BY m.location_id, t.language_id
)
SELECT location_id, t1.language_id, t1.medialist || t2.medialist AS medialist
FROM (SELECT * FROM t WHERE language_id IS NOT NULL) t1
RIGHT OUTER JOIN (SELECT * FROM t WHERE language_id IS NULL) t2 USING (location_id);
I am not sure if this does exactly what you want, but hopefully it will give you some ideas.

SQL issue - type of fkey

I'm using PostgreSQL
What I need
In SELECT query I need to select owner_type (client or domain). If solution does not exist please help me to rework this schema.
Schema (tables)
Albums - id | client_id (fkey) | domain_id (fkey) | name
Clients - id | first_name | last_name
Domains - id | name
Description: Albums owner can be Client or Domain or future other Nodes...
1. CREATE TABLE QUERY
CREATE TABLE albums
(
id BIGSERIAL PRIMARY KEY,
client_id BIGINT,
domain_id BIGINT,
name VARCHAR(255) NOT NULL,
FOREIGN KEY (client_id) REFERENCES clients(id),
FOREIGN KEY (domain_id) REFERENCES domains(id),
CHECK ((client_id IS NULL) <> (domain_id IS NULL))
);
2. SELECT QUERY
SELECT albums.id,
albums.name,
COALESCE(c.id, d.id) AS owner_id
FROM albums
LEFT JOIN clients c
ON albums.client_id = c.id
LEFT JOIN domains d
ON albums.domain_id = d.id
Need something like -> if c.id === null -> owner_type = 'Domain'
You would seem to want:
SELECT a.id, a.name,
COALESCE(c.id, d.id) AS owner_id,
(CASE WHEN c.id IS NOT NULL THEN 'client' ELSE 'domain' END) as owner_type
FROM albums a LEFT JOIN
clients c
ON a.client_id = c.id LEFT JOIN
domains d
ON a.domain_id = d.id ;
Do you need two separate columns representing client_id and domain_id for the type of owners? It seems that if you were to add more nodes, you would have to add additional columns.
Could you have an owners table representing all types of owners, and have an owner_id foreign key on the albums table?
I was thinking something like this:
CREATE TABLE albums (
id BIGSERIAL PRIMARY KEY,
owner_id BIGINT,
name VARCHAR(255) NOT NULL,
FOREIGN KEY (owner_id) REFERENCES owners(id)
);
CREATE TABLE owners (
id BIGSERIAL PRIMARY KEY,
type VARCHAR(100) NOT NULL,
name VARCHAR(255) NOT NULL
);
You could then query for albums belonging to clients:
SELECT a.id, a.name, o.name AS owner_name
FROM albums a
JOIN owners o ON o.id = a.owner_id
WHERE o.type = 'Client';
As new nodes (types of owners) are added, you simply need to add them to the owners table without modifying the schema of the albums table.
Hope this helps.

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.