Using column name as part of foreign key relationship - sql

As part of the application I am building I want to store the values of all dropdowns in a single table, where all table/column combinations that represent a dropdown will be able to reference it.
Something like the below:
CREATE TABLE dropdown_def
(
id int,
table_id int,
field_name TEXT,
value TEXT,
PRIMARY KEY (id),
UNIQUE (table_id, field_name, value)
);
INSERT INTO test1.dropdowns (id, table_id, field_name, value)
VALUES (1, 1, 'status', 'active');
CREATE TABLE clients
(
id int,
table_id int,
status TEXT,
FOREIGN KEY (table_id, 'status', status)
REFERENCES dropdowns (table_id, field_name, value)
);
I tried the above but with no surprise it seems you cannot use the actual column name as part of the foreign key constraint.
Is there any other way of using the column identifier in a FK?

You can have a constant stored computed column for the FK.
CREATE TABLE clients
(
id int,
table_id int,
status TEXT,
refcolname text GENERATED ALWAYS AS ('status') STORED,
FOREIGN KEY (table_id, refcolname, status)
REFERENCES dropdowns (table_id, field_name, value)
);
Really I would advice against the design like that. Why to create a united table of lookups just to get troubles referencing its parts. Use the EAV pattern only if it is absolutely inevitable, for example a set of lookup tables is to be defined by a user at runtime.

Related

PostgreSQL ensure data integrity for shared primary key

Let's say we want to use globally unique id and find the type of entity using it:
CREATE TABLE identity (
id serial PK NOT NULL,
type IdentityEnum NOT NULL,
UNIQUE (id, type)
);
CREATE TABLE user (
id integer PK NOT NULL REFERENCES identity (id),
type IdentityEnum NOT NULL,
UNIQUE (id, type)
ALTER TABLE user
add constraint identity_fk
foreign key (id, type)
REFERENCES identity (id, type);
);
CREATE TABLE animal (
id integer PK NOT NULL REFERENCES identity (id),
type IdentityEnum NOT NULL,
UNIQUE (id, type)
ALTER TABLE animal
add constraint identity_fk
foreign key (id, type)
REFERENCES identity (id, type);
);
To ensure that user or animal tables will have one to one relation to identity:
it's will not be possible by more that one table to point at the same identity PK
We are forced to add additional type column for each table, add UNIQUE (id, type) and add FK from each table to identity.
Questions:
Is there another way to design database with a shared by multiple tables id without the need for type?
How to do joins if you have only id and don't know the type?
1
You do not need the type columns in any table. The type will appear in a view by the fact that the complementary data are stored into the animal or user table. This is known as an inherited data modeling. The "IDENTITY" table is the father table, while user and animal are children tables.
But to make it working properly you need to add triggers for INSERT / UPDATE that exclude the same ID velue to be present in the two child tables.
2
Use the view

How to model polymorphic data in PostgreSQL

We have some data that we'd like to model in the following way. We have two fields:
to
from
and each of those fields have the following data:
some enum
some foreign key (one of 4 possible tables)
Does PostgreSQL provide a way to store this polymorphic enum + dynamic foreign key?
You can use json (or jsonb or array) for store 2 fields in "from" and "to" columns.
Then you can use generated columns to store one fk for each table.
Something like this:
create table table1 (
id serial primary key,
name text
);
insert into table1 values (1,'one'),(2,'two');
create table table2 (
id serial primary key,
name text
);
insert into table2 values (1,'one'),(2,'two');
create table the_table(
"from" jsonb,
from1 bigint generated always as (case when "from"->>'enum' = '1' then ("from"->>'data')::bigint else null end) stored,
from2 bigint generated always as (case when "from"->>'enum' = '2' then ("from"->>'data')::bigint else null end) stored,
foreign key (from1) references table1 (id),
foreign key (from2) references table2 (id)
);
insert into the_table("from") values ('{"enum":1, "data":2}');

SQLite database with multi-valued properties

I want to create a SQLITE database for storing objects. The objects have properties with multiple values for which I have created separate tables.
CREATE TABLE objs
(
id INTEGER,
name TEXT
);
CREATE TABLE prop1
(
id INTEGER,
value TEXT,
FOREIGN KEY(id) REFERENCES objs(id)
);
CREATE TABLE prop2
(
id INTEGER,
value TEXT,
FOREIGN KEY(id) REFERENCES objs(id)
);
For a list of ids I get as a result of JOINs, I want to find values of these two properties. For that, I am performing the JOINs followed by another JOIN with the 'prop1' table. I then repeat this for 'prop2' table. I suspect this is inefficient (too many joins) and can be improved. I have two questions.
Is this the correct way to design the DB ?
What is the most efficient way of extracting values of the properties I want ?
I would suggest the following structure.
CREATE TABLE objs
(
id INTEGER,
name TEXT
);
CREATE TABLE properties
(
id INTEGER,
Property_name varchar(50),
Property_type varchar(10),
value TEXT,
FOREIGN KEY(id) REFERENCES objs(id)
);
Storing all the different types of properties in different table is a very bad idea. You can just store the property name and type(string, numeric etc.). You can also add multiple value columns like numeric_value, string_value and so on.

No auto increment in my foreign key

I'm working in my school project and I want to make the id subject of any student incremented automatically as foreign key. I am showing you the example below as prototype, there are two tables, when I'm trying to insert data into the second table, I get an error (necessary to insert another field id of the table)
CREATE DATABASE database1;
USE database1;
CREATE TABLE table1
(
IdTable1 INT NOT NULL IDENTITY,
NOM VARCHAR(30),
PRIMARY KEY(IDMEDO)
);
--auto increment is working here
INSERT INTO table1
VALUES ('data1Table1'), ('data2Table1'), ('data3Table1');
--auto increment is working here just with the primary key
CREATE TABLE table2
(
IdTable2 INT not null IDENTITY,
IdTable1 INT,
dataTable2 varchar(30),
primary key(IdTable2),
constraint fk_tbl1 foreign key(IdTable1) references Table1
);
--necessary to add id field
INSERT INTO table2
VALUES ('data1Table2'), ('data2Table2'), ('data3Table2');
You should always (as a "best practice") define the columns you want to insert data into - that way, you can specify those that you have to provide values for, and let SQL Server handle the others. And for the foreign key, you have to explicitly provide a value - there's no "auto-magic" (or "auto-incrementing") way of associating a child row with its parent table - you have to provide the value in your INSERT statement.
So change your code to:
-- explicitly *specify* the NOM column here!
INSERT INTO table1 (NOM)
VALUES ('data1Table1'), ('data2Table1'), ('data3Table1');
-- again: explicitly *specify* the columns you want to insert into!
INSERT INTO table2 (IdTable1, dataTable2)
VALUES (1, 'data1Table2'), (2, 'data2Table2'), (3, 'data3Table2');
You need to insert the value of the foreign key wrt to table 1 now if you enter a value that's not in table 1 column which is acting as a foreign key then also it will not accept
Simply
Primary key and foreign key should match
Foreign key can't be null if table 1 doesn't contain a primary key with value null

Add relation with fixed column value

I like to create a 'conditional' (foreign key) relation between 3 tables. In my case, it's like this (of course it's quite more complex, but I've stripped it down to demonstrate the problem situation):
Table [ItemTable]
Column int Id (PK)
Column str ItemName
Table [ItemGroup]
Column int Id (PK)
Column str GroupName
Table [Settings]
Column int Id (PK)
Column str RefersTo ('I' means item, 'G' means item group)
Column int Reference (foreign key depending on 'RefersTo')
The Goal now is to create Relations with contraints like this:
Settings.Reference refers to ItemTable.Id when Settings.RefersTo equals 'I'
Settings.Reference refers to ItemGroup.Id when Settings.RefersTo equals 'G'
No relation in case if RefersTo is empty (so no constraint in this situation)
It sounds like a refer-here-or-there-relation, but I don't know how to achive with MS SQL. I usually use the grafical designer in Management Studio to create and modify table defintion.
Any help is appreciated. Thank you in advance.
Foreign keys don't have filter clauses in their definition. But you can do this using computed columns:
create table Settings as (
. . .
reference_i as (case when refersto = 'I' then reference end) persisted,
reference_g as (case when refersto = 'G' then reference end) persisted,
constraint fk_settings_reference_index
foreign key (reference_i) references itemTable(id),
constraint fk_settings_reference_group
foreign key (reference_g) references groupTable(id)
);
This is not a good design and if you can, it would be better to change it as #VojtěchDohnal already suggested.
If you cannot change it, you could use a trigger after insert, to check if the corresponding value of Reference comes from the correct table, depending on the current value of RefersTo and if not, stop inserting and throw some error, but using triggers is also not the best way performance-wise.
You cannot use an indexed view (which would have been the best, since it would be schema bound and it would get all new values/deleted values from your items or groups) since your sources are two different ones and you would need a union to generate a full list of posible values and there's a limitation that The SELECT statement in the view definition must not contain UNION
in indexed views.
The last option: You could use an additional table where you keep all data (Type('I', 'G'), Value (Id's from ItemTable for 'I', Id's from ItemGroup for 'G')) with possible Id's for each table and then make your composite foreign key refer to this new table.
The drawback is that in this case you would need to keep track of changes in both ItemTable and ItemGroup tables and update the newly created table accordingly (for newly inserted values, or deleted values) which is not so nice when it comes to maintenance.
For this last scenario the code would be something like:
CREATE TABLE ItemTable (Id INT PRIMARY KEY IDENTITY(1,1), ItemName VARCHAR(100))
CREATE TABLE ItemGroup (Id INT PRIMARY KEY IDENTITY(1,1), GroupName VARCHAR(100))
CREATE TABLE Settings (Id INT PRIMARY KEY IDENTITY(1,1), RefersTo CHAR(1), Reference int)
INSERT INTO ItemTable (ItemName) values ('TestItemName1'), ('TestItemName2'), ('TestItemName3'), ('TestItemName4')
INSERT INTO [ItemGroup] (GroupName) values ('Group1'), ('Group2')
SELECT * FROM ItemTable
SELECT * FROM ItemGroup
SELECT * FROM Settings
CREATE TABLE ReferenceValues (Type char(1), Val INT, PRIMARY KEY (Type, Val))
INSERT INTO ReferenceValues
SELECT 'I' as Type, i.Id as Val
FROM dbo.ItemTable i
UNION
SELECT 'G' as Type, g.Id as Val
FROM dbo.ItemGroup as g
ALTER TABLE dbo.Settings
ADD FOREIGN KEY (RefersTo, Reference) REFERENCES dbo.ReferenceValues(Type, Val);
INSERT INTO Settings (RefersTo, Reference)
VALUES ('I', 1) -- will work
INSERT INTO Settings (RefersTo, Reference)
VALUES ('G', 4) -- will not work
After thinking arround, I came to conclusion to discard the whole idea with one-column-multi-relation thingy.
Answer accepted: Despite on good or bad idea, implementation as desired not possible :)
Thank you all for your answers and comments!