How to query parent & child table in one query? - sql

First, I'll address concerns about duplicates:
How to query a parent table and inherited child table together in one query
This question is similar but it doesn't provide a concrete example
How can you represent inheritance in a database? suggests "Class Table Inheritance", which is the pattern I'm using, but does not explain how to query it effectively.
Here's a example of the problem I'm facing:
table Document {
id: Id
name: string
type: ??
}
table FooDoc {
id: Id
// Foreign key to Document
docId: Id
qux: string
}
table BarDoc {
id: Id
// Foreign key to document
docId: Id
baz: number
}
Ideally, I'd like to make it so that in 1 query, I can
grab a document based on its id
grab the relevant data from the correct child table
Is this possible?

There are six ways (afaik) to model table inheritance in relational databases. You chose the Permissive Class Table Inheritance option.
Now, you can use two left joins to retrieve information for child tables. The resulting columns from the non-matching type will be null.
For example:
select d.*, f.qux, b.baz
from document d
left join foodoc f on f.id = d.id
left join bardoc b on b.id = d.id
Result:
id name type qux baz
--- ----- ----- -------- ----
20 baz1 2 null 1240
10 foo1 1 content null
See running example at DB Fiddle. As you can see, column qux is null for type 2 and column baz is null for type 1.
The sample structure for this example is shown below:
create table document (
id int primary key not null,
name varchar(10),
type int not null check (type in (1, 2))
);
insert into document (id, name, type) values
(10, 'foo1', 1),
(20, 'baz1', 2);
create table foodoc (
id int primary key not null references document(id),
qux varchar(10)
);
insert into foodoc (id, qux) values (1, 'content');
create table bardoc (
id int primary key not null references document(id),
baz int
);
insert into bardoc (id, baz) values (2, 1240);
Note: Also please consider that to fully implement integrity you would need to include the type column in both foreign keys.

Related

How to efficiently insert ENUM value into table?

Consider the following schema:
CREATE TABLE IF NOT EXISTS snippet_types (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS snippets (
id INTEGER NOT NULL PRIMARY KEY,
title TEXT,
content TEXT,
type INTEGER NOT NULL,
FOREIGN KEY(type) REFERENCES snippet_types(id)
);
This schema assumes a one-to-many relationship between tables and allows efficiently maintaining a set of ENUMs in the snippet_types table. Efficiency comes from the fact that we don't need to store the whole string describing snippet type in the snippets table, but this decision also leads us to some inconvenience: upon inserting we need to retrieve snippet id from snippet_types and this leads to one more select and check before inserting:
SELECT id FROM snippet_types WHERE name = "foo";
-- ...check that > 0 rows returned...
INSERT INTO snippets (title, content, type) values ("bar", "buz", id);
We could also combine this insert and select into one select like that:
INSERT INTO snippets (title, content, type)
SELECT ("bar", "buz", id) FROM snippet_types WHERE name = "foo"
However, if "foo" type is missing in snippet_types then 0 rows would have been inserted and no error returned and I don't see a possibility to get a number of rows sqlite actually inserted.
How can I insert ENUM-containing tuple in one query?

How to make a table share a sequence with another table in postgresql?

Basically I have a table1 with unique ids of features for a city, now I got a table2 for features for that country.
I need to create new ids for the country table (these need to share the same sequence as the city, so that the ids match when cross referencing tables)
How to make table2 have the same ids as table1 within that city and then new ids for features elsewhere? Essentially sharing the sequence
Edit: the tables are already created, how can i update table2
If you manually create a sequence and assign that as the default value to the ID columns, then it works. But to reuse an existing value that would mean we'd have to create a trigger that assign an existing value or obtains a new from the shared sequence.
create sequence baz;
create table foo(id bigint default nextval('baz'), value text);
create table bar(id bigint default nextval('baz'), value date);
insert into foo (value) values ('Hello');
insert into bar (value) values (now());
insert into foo (value) values ('World');
insert into bar (value) values (now());
select 'foo', id, value::text from foo
union all
select 'bar', id, value::text from bar
And the result is:
foo 1 Hello
bar 2 2018-10-29
foo 3 World
bar 4 2018-10-29
And as a bonus:
drop sequence baz
ERROR: cannot drop sequence baz because other objects depend on it
Detail:
default for table foo column id depends on sequence baz
default for table bar column id depends on sequence baz
Hint: Use DROP ... CASCADE to drop the dependent objects too.
Edit: If we can do post processing then this approach could be usedto assign values for the missing ID columns:
update bar
SET id = coalesce((select id from foo where bar.city_name = foo.city_name),nextval('baz'))
WHERE id is null
if your tables are already created you must create a sequence
create sequence seq_city_country;
and then add the sequence to your ids with the following code
ALTER TABLE city ALTER COLUMN id_city SET DEFAULT nextval('seq_city_country');
ALTER TABLE country ALTER COLUMN id_country SET DEFAULT nextval('seq_city_country');
if your sequence is already created for table city by (sequence_c) you can use
ALTER TABLE country ALTER COLUMN id_country SET DEFAULT nextval('sequence_c');
CREATE SEQUENCE shared_seq;
CREATE TABLE a (
col1 bigint DEFAULT nextval('shared_seq'),
...
);
CREATE TABLE b (
col1 bigint DEFAULT nextval('shared_seq'),
...
);
This doesn't sound like very good (or even possible) database design. Instead, I suggest creating a junction table which relates cities to their respective countries. So, your three tables might look like this:
city (PK id, name, ...)
country (PK id, name, ...)
country_city (city_id, country_id) PK (city_id -> city(id), country_id -> country(id))
With this design, you don't need to worry about the auto increment sequences in the city and country table. Just let Postgres assign those values, and then just maintain the junction table using the correct values.

Compare values from 2 tables and produce a new table with the differences

I'm trying to find a way in SQL Server (script) to:
Given 2 tables, with identical column structure
Filled with rows sharing an identical key
But with different values in one of the columns (INTEGER)
Find rows with the matching key in both tables and compare them to produce a new row. in a different table, with the exact same matching values in the rest of the columns and the difference in value between the only non-matching column.
Ex. It's the same db in 4 versions, every new iteration accumulating the values of the previous version, what I'm trying to achieve is a new db with the difference (like in db1 the amount of apples sold is 5, and in db2 the amount of apples sold is 20, I need a new row in a new table with a value of 15).
CREATE TABLE foo (
theKey int NOT NULL IDENTITY(1,1) PRIMARY KEY,
someInt int
)
CREATE TABLE bar (
theKey int NOT NULL IDENTITY(1,1) PRIMARY KEY,
otherInt int
)
CREATE TABLE output (
theKey int NOT NULL PRIMARY KEY,
someInt int,
otherInt int,
valueDifference int
)
INSERT INTO output ( theKey, someInt, otherInt, valueDifference )
SELECT
foo.theKey,
foo.someInt,
bar.otherInt,
foo.someInt - bar.otherInt AS valueDifference
FROM
foo
INNER JOIN bar ON foo.theKey = bar.theKey

SQL Database multiple values for same attribute - Best practices?

I have found myself that some attributes from my Person table, need to hold multiple values/choices, which is not a good SQL practice so I created a second table, like this:
Before:
Person table
-ID (ex. 101)
-Name (ex. John)
-Accessories (ex. Scarf, Mask, Headband, etc..) - One person can have a combination of this
After:
Person Table
-ID
-Name
PersonDetails Table
-PersonID (FK to Person table)
-Attribute type
-Attribute value
and an example:
Person:
ID:13; Name: John Snow
PersonDetails:
PersonID: 13; Attribute type: Accessories; Attribute value: Scarf
PersonID: 13; Attribute type: Accessories; Attribute value: Mask
You can see that person with ID 13 has both Scarf and Mask.
Is this a good practice? What other ways are there to do this the most efficiently?
Also, what ways are there if an update comes up and Person with 13 doesn't have Scarf and Mask but only Glasses? (Delete the 2 separately and insert a new one? that means 3 queries for only one modify request)
I think this is rather n:m-related. You'd need one table Person holding ID, name and other person's details. Another table Accessory with ID, name and more accessory's details. And a third table PersonAccessory to store pairs of PersonID and AccessoryID (this is called mapping table)
Working example (SQL-Server syntax)
CREATE TABLE Person(ID INT IDENTITY PRIMARY KEY,Name VARCHAR(100));
INSERT INTO Person VALUES('John'),('Jim');
CREATE TABLE Accessory(ID INT IDENTITY PRIMARY KEY,Name VARCHAR(100));
INSERT INTO Accessory VALUES('Scarf'),('Mask');
CREATE TABLE PersonAccessory(PersonID INT NOT NULL FOREIGN KEY REFERENCES Person(ID)
,AccessoryID INT NOT NULL FOREIGN KEY REFERENCES Accessory(ID));
INSERT INTO PersonAccessory VALUES(1,1),(2,1),(2,2);
SELECT p.Name
,a.Name
FROM PersonAccessory AS pa
INNER JOIN Person AS p ON pa.PersonID=p.ID
INNER JOIN Accessory AS a ON pa.AccessoryID=a.ID;
GO
--DROP TABLE PersonAccessory;
--DROP TABLE Accessory;
--DROP TABLE Person
The result
John Scarf
Jim Scarf
Jim Mask
Here is a working example. Check this out
;with tmp(Personid, name,AttributeType, DataItem, Data) as (
select Personid, name,'Accessories' AttributeType, LEFT(Accessories, CHARINDEX(',',Accessories +',')-1),
STUFF(Accessories , 1, CHARINDEX(',',Accessories +','), '')
from Person
union all
select Personid, name,'Accessories' AttributeType, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from tmp
where Data > ''
)
select Personid, name,AttributeType, DataItem
from tmp
order by Personid

Define foreign key in Postgres to a subset of a target table

Example:
I have:
Table A:
int id
int table_b_id
Table B:
int id
text type
I want to add a constraint check on column table_b_id that will verify that it points only to rows in table B which their type value is 'X'.
I can't change table structure.
I've understood it can be done with 'CHECK' and a postgres functions which will do the specific query but I've saw people recommending to avoid it.
Any inputs on what is the best approach to implement it will be helpful.
What you are referring to is not a FOREIGN KEY, which, in PostgreSQL, refers to a (number of) column(s) in an other table where there is a unique index on that/those column(s), and which may have associated automatic actions when the value(s) of that/those column(s) change (ON UPDATE, ON DELETE).
You are trying to enforce a specific kind of referential integrity, similar to what a FOREIGN KEY does. You can do this with a CHECK clause and a function (because the CHECK clause does not allow sub-queries), you can also do it with table inheritance and range partitioning (refer to a child table which holds only rows where type = 'X'), but it is probably the easiest to do this with a trigger:
CREATE FUNCTION trf_test_type_x() RETURNS trigger AS $$
BEGIN
PERFORM * FROM tableB WHERE id = NEW.table_b_id AND type = 'X';
IF NOT FOUND THEN
-- RAISE NOTICE 'Foreign key violation...';
RETURN NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE tr_test_type_x
BEFORE INSERT OR UPDATE ON tableA
FOR EACH ROW EXECUTE PROCEDURE trf_test_type_x();
You can create a partial index on tableB to speed things up:
CREATE UNIQUE INDEX idx_type_X ON tableB(id) WHERE type = 'X';
The most elegant solution, in my opinion, is to use inheritance to get a subtyping behavior:
PostgreSQL 9.3 Schema Setup with inheritance:
create table B ( id int primary key );
-- Instead to create a 'type' field, inherit from B for
-- each type with custom properties:
create table B_X ( -- some_data varchar(10 ),
constraint pk primary key (id)
) inherits (B);
-- Sample data:
insert into B_X (id) values ( 1 );
insert into B (id) values ( 2 );
-- Now, instead to reference B, you should reference B_X:
create table A ( id int primary key, B_id int references B_X(id) );
-- Here it is:
insert into A values ( 1, 1 );
--Inserting wrong values will causes violation:
insert into A values ( 2, 2 );
ERROR: insert or update on table "a" violates foreign key constraint "a_b_id_fkey"
Detail: Key (b_id)=(2) is not present in table "b_x".
Retrieving all data from base table:
select * from B
Results:
| id |
|----|
| 2 |
| 1 |
Retrieving data with type:
SELECT p.relname, c.*
FROM B c inner join pg_class p on c.tableoid = p.oid
Results:
| relname | id |
|---------|----|
| b | 2 |
| b_x | 1 |