Add unique constraint on fields from different tables - sql

I have two tables/entities:
One table Users with these 3 fields :
id | login | external_id
There is a unique constraint on external_id but not on login
And another table User_Platforms that have these 3 fields :
id | user_id | platform_name
There is a #OneToMany relation between Users and Platforms. ( One user can have multiple platforms).
Is there a way to put a unique constraint on the fields login ( from Users table) and platform_name ( from User_Platforms table) to avoid having multiple users with the same login on the same platform ?
I was thinking of duplicating login field inside User_Platforms table to be able to do it easily. Is there a better way ?

UNIQUE constraints cannot span multiple tables. In the model you are presenting "as is" it's not possible to create a unique constraint that will ensure data quality.
However, if you are willing to add redundancy to the data model you can enforce the rule. You'll need to add the column login as a redundant copy in the second table. This will change the way you insert data in the second table, but will ensure data quality.
For example:
create table users (
id int primary key not null,
login varchar(10) not null,
external_id varchar(10) not null,
constraint uq1 unique (id, login)
-- extra UNIQUE constraint for redundancy purposes
);
create table user_platforms (
id int primary key not null,
user_id varchar(10) not null,
platform_name varchar(10) not null,
login varchar(10) not null, -- new redundant column
constraint fk1 foreign key (id, login) references users (id, login),
-- FK ensures that the redundancy doesn't become stale
constraint uq2 unique (platform_name, login) -- finally, here's the prize!
);

Related

Storing foreign key linked in a separate table? SQL

So I went through Odoo database design and I saw that they stored the relationship between 2 tables in a separate table that doesn't have primary key. How are you able to do this? I want to replicate this kind of behavior in SQL Server. Is it auto-inserted?
A table should always have a primary key. Look at the thousands of questions on Stackoverflow that ask how to delete one of two identical rows in a database table.
You typically model m-to-n relationships between tables with a separate table that has foreign keys to both tables:
CREATE TABLE person (
person_id bigint PRIMARY KEY,
name text,
...
);
CREATE TABLE game (
game_id bigint PRIMARY KEY,
name text NOT NULL,
...
);
CREATE TABLE likes_to_play (
person_id bigint REFERENCES person NOT NULL,
game_id bigint REFERENCES game NOT NULL,
PRIMARY KEY (person_id, game_id)
);
CREATE INDEX ON likes_to_play (game_id);
The primary key on the table makes sure there are no superfluous double entries and backs one of the foreign keys. The other index is created for the other foreign key.

two foreign keys to the same numeric data type and reference it to two tables

it´s possible create two foreign keys to the same type of numerical data in a table and reference it to two tables and I have this exemples in down
create table admin and primary key
/* table admin*/
create table admin (id_admin number(10) not null,
email_admin varchar(30) not null,
password_admin varchar(10) not null);
/* primary key */
alter table admin add constraint admin_pk primary key (id_admin);
create table user and primary key
/* table user*/
create table user (id_user number(10) not null,
email_user varchar(30) not null,
password_user varchar(10) not null);
/* primary key */
alter table user add constraint user_pk primary key (id_user);
create table login and primary key and foreign key
/* table login*/
create table login(id_login number(10) not null,
id_admin_user_login number(10) not null,
email_login varchar(20) not null,
password_login varchar(10) not null);
/* primary key */
alter table login add constraint login_pk primary key (id_login);
/* foreign key reference to admin*/
alter table login add constraint login_fk_admin foreign key (id_admin_user_login)
reference admin(id_admin);
/* foreign key reference to user*/
alter table login add constraint login_fk_user foreign key (id_admin_user_login)
reference user(id_user);
is possible?
Your data model doesn't seem to make a lot of sense. You have three different entities admin, user, and login. All three of them appear to store the same information-- an email address, a username, and a password (which I hope for basic security is really a password hash). If there are any relationships between the tables, that violates basic normalization because you'd be storing the same information in multiple places.
Not knowing the business requirements for the entities you're actually trying to model, it is difficult to know precisely what you want.
My first guess is that you have two types of users, admins and regular users, each of which can log in to your application. Assuming that the attributes of users are pretty consistent regardless of their role (both admins and regular users have email addresses, passwords, etc.) the simplest way to model that would be with a single login table with a login_type that tells you whether a particular user is an admin or a regular user
create table login (
login_id integer primary key,
email varchar2(255),
password_hash raw(32),
login_type varchar2(1) check( login_type IN ('A', 'U') )
);
You can make that a bit more flexible by creating a lookup table for the login types that your login table references
create table login_type_lkup (
login_type_code varchar2(1) primary key,
login_type_desc varchar2(255)
);
create table login (
login_id integer primary key,
email varchar2(255),
password_hash raw(32),
login_type_code varchar2(1) references login_type_lkup( login_type_code )
);
If you want more flexibility, the next step would be to say that logins don't really have a type. Instead, they have one or more role that has some set of permissions. You might have an admin role and a regular user role initially but later want to add a read only user role, a superuser role, etc. In that case, you'd have something like
create table login (
login_id integer primary key,
email varchar2(255),
password_hash raw(32)
);
create table role (
role_id integer primary key,
role_desc varchar2(255)
);
create table permission (
permission_id integer primary key,
permission_desc varchar2(255)
);
create table login_role (
login_id integer references login(login_id),
role_id integer references role(role_id),
primary key pk_login_role( login_id, role_id )
);
create table role_permission (
role_id integer references role(role_id),
permission_id integer references permission(permission_id),
primary key pk_role_permission( role_id, permission_id )
);
Having FK to 2 tables in this case defeats the purpose - a person must be a user and an admin at the same time. Admin or user are in fact roles, so you can have, for instance (oversimplified example, just to illustrate approach. you can find more by searching party-role-relationship model) ,
PARTY (party_id (PK), name, ... .);
PARTY_LOGIN(party_login_id(PK), party_id (FK to PARTY), ...);
PARTY_ROLE(party_id (PK,FK), role_id(PK,FK)) ;

Postgresql multiple tables with same foreign key unique constraint

I have following tables on PostgreSQL 9.4
CREATE TABLE "user" (
id SERIAL PRIMARY KEY,
email CHARACTER VARYING NOT NULL,
password CHARACTER VARYING NOT NULL
);
CREATE TABLE "dealer" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES "user" (id) ON DELETE RESTRICT
);
CREATE TABLE "affiliate" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES "user" (id) ON DELETE RESTRICT
);
Is it possible to force user_id value to be unique across tables dealer and affiliate?
There are different setups to use for inheritance in SQL and for this you could just use an integer column type in the table user that marks the type of the user and would reference to table user_type (id,name) that would have the values 1,dealer and 2,affiliate:
CREATE TABLE user_type (
id INTEGER PRIMARY KEY, --could be SERIAL
name text
);
INSERT INTO user_type VALUES (1,'dealer'), (2, 'affiliate');
CREATE TABLE "user" (
id SERIAL PRIMARY KEY,
email CHARACTER VARYING NOT NULL,
password CHARACTER VARYING NOT NULL,
user_type INTEGER REFERENCES user_type NOT NULL,
UNIQUE(id,user_type)
);
This in itself wouldn't force uniqueness across tables so after implementing this you would have the following options:
Drop the tables dealer and affiliate - you won't need them if you rely on the type field to see which one the user is.
If you have to keep those inherited tables you can:
Use triggers - these triggers check the uniqueness and would be actived on INSERT or UPDATE
Another (a bit clumsy) solution: add user_type field to both subtables like this:
CREATE TABLE "dealer" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
user_type INTEGER NOT NULL DEFAULT 1 check (user_type = 1),
FOREIGN KEY (user_id,user_type) REFERENCES "user"(id,user_type) ON DELETE RESTRICT
);
CREATE TABLE "affiliate" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
user_type INTEGER NOT NULL DEFAULT 2 check (user_type = 2),
FOREIGN KEY (user_id,user_type) REFERENCES "user"(id,user_type) ON DELETE RESTRICT
);
The checks and foreign keys together make sure you cannot have both types of user in the main table. Note that user_id might be used as the PRIMARY KEY in the subtables too. Currently a row in user might have several dealer rows linked to it so at least you might want to set user_id foreign keys in subtables as UNIQUE.

Filesystem like permissions in DBMS

Sorry for a big question, but I can't explain my situation with less information.
I designed a database system that is like this:
CREATE TABLE servers(
ID bigint primary key not null
/* other fields of the server */
);
CREATE TABLE producers(
ID bigint not null,
ServerID bigint not null,
/* other field of the producer */
constraint "PK_producers"
primary key (ID, ServerID),
constraint "FK_producer_servers"
foreign key("ServerID") references "servers"
);
CREATE TABLE records(
ID bigint primary key,
ServerID bigint not null,
ProducerID bigint not null
/* other fields of record */
constraint "FK_records_servers"
foreign key("ServerID") references "servers"
constraint "FK_records_producers"
foreign key("ServerID", "ProducerID") references "producers"
);
CREATE TABLE groups(
ID bigint not null primary key,
GroupName nvarchar(50) not null,
Permissions int not null
/* other fields of the group */
);
CREATE TABLE users(
ID bigint not null primary key,
UserName nvarchar(50) not null unique,
Permissions int not null
/* other fields of user */
);
CREATE TABLE users_in_groups(
UserID bigint not null,
GroupID bigint not null,
constraint "PK_users_in_groups" primary key (UserID, GroupID),
constraint "FK_uig_users" foreign key("UserID") references "users",
constraint "FK_uig_groups" foreign key("GroupID") references "groups"
);
Data will be added to records from producers and each producer may provide 500~2000 records per day and each server usually have 2~4 producer and it is completely normal to have 3~6 server. As you see records table is growing really fast(I periodically archive some data and shrink database but records table usually have > 100000 record).
Now my question is:
As you see users have different permissions, based on which group they currently belong and which permissions applied to them by administrators, now I have to advance this system in a way that each user may have different permissions to different items(server, producer and record) in a way that if a user have explicit permission to an item use that permission otherwise use permission from parent and at least global permissions of the user. For example user permissions to a record will be indicated from permissions that applied to that record, its producer, its server or permissions that defined for the user.
As first workaround I think I will implement relation tables that provide relation between user and various objects as follow:
CREATE TABLE user_server_permissions(
UserID bigint not null,
ServerID bigint not null,
Permissions int not null,
constraint "PK_usp" primary key ("UserID", "ServerID"),
constraint "FK_usp_users" foreign key ("UserID") references "users",
constraint "FK_usp_server" foreign key ("ServerID") references "servers"
);
CREATE TABLE user_producer_permissions(
UserID bigint not null,
ServerID bigint not null,
ProducerID bigint not null,
Permissions int not null,
constraint "PK_upp" primary key ("UserID", "ServerID", "ProducerID"),
constraint "FK_upp_users" foreign key ("UserID") references "users",
constraint "FK_upp_server" foreign key ("ServerID") references "servers"
constraint "FK_upp_producer" foreign key ("ServerID", "ProducerID") references "producers"
);
CREATE TABLE user_record_permissions(
UserID bigint not null,
RecordID bigint not null,
Permissions int not null,
constraint "PK_urp" primary key ("UserID", "ServerID"),
constraint "FK_urp_users" foreign key ("UserID") references "users",
constraint "FK_urp_record" foreign key ("RecordID") references "records"
);
But using this approach really decrease performance, because main table that I work with it is records and it is a rare condition that an administrator set special permission for a record and most records should use permissions from their producer but using this technique I should check multiple tables for each access to records table. For example a simple query like:
SELECT * FROM "records" WHERE /* Some condition */ AND record_is_accessible( "ID" )
Will kill my server that should be able to respond to multiple clients!
Most my clients use MSSQL as DBMS but there are few cases where they use MySQL or ORACLE.
Now can anyone direct me in a way to accomplish the task!?
At first glance, a quick solution would be
to not have 3 separate tables for permissions. Instead you would have this one
CREATE TABLE user_entity_permissions(
UserID bigint not null,
EntityID bigint not null,
EntityType int not null,
Permissions int not null,
constraint "PK_usp" primary key ("UserID", "EntityID"),
constraint "FK_usp_users" foreign key ("UserID") references "users"
--- Third FK constraint ---
);
Now you want to shape your Third Fk constraint accordingly (if needed)
For example lets say you give Servers an EntityType = 1, Producer --> 2, Record --> 3
Then EntityId and EntityType = 1 references to servers and so on...
This way you can also sort the priority of your permissions by using EntityType (e.g. 1 first, then 2, then 3)

SQL Foreign key issue with 2 parent tables

I have have 2 tables User and Group.
I have a table Attributes shared by user and group with columns:
attributeName.
AttributeValue.
ObjectID.
ObjectID points to either the primary key of user or the primary key of Group.
I have added a foreign constraint with Cascade on Delete in order to delete automatically the attributes when user or a group is deleted.
The problem now is when I insert an attribute for the user, I have a foreign key constraint because the group does not exist.
How should I proceed?
You have basically 3 options:
Keep your current design, but replace Attribute.ObjectID with UserID and GroupID, attach a separate FK to each of them (one towards Group and the other towards User) and allow either to be NULL. You'd also want a CHECK constraint to ensure not both of them are NULL.
Split Attribute table to UserAttribute and GroupAttribute, thus separating each foreign key into its own table.
Use inheritance, like this:
The solution (1) is highly dependent on how your DBMS handles UNIQUE on NULLs and both (1) and (2) allow the same AttributeName to be used for two different attributes, one for user an the other for group.
As you have discovered you can not have one column as foreign key to two different tables. You can't add a attribute for a user when it does not exist a group with the same id. And you can of course not know if the attribute is for a user or a group.
From comments you also mentioned a m:m relation between user and group so I would suggest the following.
create table [User]
(
UserID int identity primary key,
Name varchar(50) not null
)
go
create table [Group]
(
GroupID int identity primary key,
Name varchar(50) not null
)
go
create table UserGroup
(
UserID int not null references [User](UserID),
GroupID int not null references [Group](GroupID),
primary key (UserID, GroupID)
)
go
create table UserAttribute
(
UserAttributeID int identity primary key,
Name varchar(50) not null,
Value varchar(50) not null,
UserID int not null references [User](UserID) on delete cascade
)
go
create table GroupAttribute
(
GroupAttributeID int identity primary key,
Name varchar(50) not null,
Value varchar(50) not null,
GroupID int not null references [Group](GroupID) on delete cascade
)
Note: The use of an attribute table should be for attributes you don't know before hand. All the stuff you know will be attributes should be fields in the actual table instead. Reserve the use of the attributes for customer defined attributes.
I think you should allow NULL values for this foreign key field ObjectId, so that you can insert any row with ObjectId = null that not referencing any user or group.
For a better design you should remove this ObjectId column, add a new column AttributeId to the two tables User and Group.