SQL Server CHECK Constraint on value from a column not referenced explicitly - sql

Similar question has been asked here: constraint check against values of foreign key , and here Having data from another table put in into check constraint , but it's not exactly what I mean (or whether there's any other way than a function).
My question is. Instead of referencing composite FOREIGN KEY, and then checking for the value of a referenced column via CHECK constraint, can I reference surrogate PK from Table A in Table B and check against a value of the column in Table A in a row identified uniquely by a surrogate PK from Table A called e.g. [test_table_a_id]
In other words. Can I have a CHECK CONSTRAINT in Table B without referencing specific column explicitly, but instead referencing id of a row and looking up the value in Table A. (Hope I am not being too consusing with the problem description)
-- ===============================
-- ---------- TABLE A ------------
CREATE TABLE [r_Test_Table_A](
[test_table_a_id] [int] IDENTITY(1,1)
CONSTRAINT [PK_r_test_table_a] PRIMARY KEY CLUSTERED ([test_table_a_id] ASC)
, [a_attribute_1] [nvarchar] (50) NOT NULL
, [a_attribute_2] [nvarchar] (50) NOT NULL
, CONSTRAINT [ak_r_Test_Table_A_a_attribute1_a_attribute_2]
UNIQUE ([a_attribute_1], [a_attribute_2])
)
-- ===============================
-- ---------- TABLE B ------------
CREATE TABLE [r_Test_Table_B](
[test_table_b_id] [int] IDENTITY(1,1)
CONSTRAINT [PK_r_test_table_b] PRIMARY KEY CLUSTERED ([test_table_b_id] ASC)
, [b_attribute_1] [nvarchar] (50) NOT NULL
, [a_attribute_1] [nvarchar] (50) NOT NULL
CONSTRAINT [ch_Test_Table_A_a_attribute_1] CHECK ([a_attribute_1] = 'Cookie')
, [a_attribute_2] [nvarchar] (50) NOT NULL
, CONSTRAINT [fk_comp_r_Test_Table_B_r_Test_Table_A]
FOREIGN KEY ([a_attribute_1], [a_attribute_2])
REFERENCES [r_Test_Table_A]([a_attribute_1], [a_attribute_2])
)

Related

How to use constraints to force two child items be from the same parent?

I have a Jobs table that holds jobs.
I have a Tasks table that holds tasks that belong to a job (1:many).
I have a Task_Relationships table that holds the data about which tasks depend on other tasks within a job.
I have 2 jobs, each job has 3 tasks and within the jobs the tasks are related as in the diagram. The Task_Relationships table is to represent that tasks within a job have dependencies between them.
How to ensure that when I add an entry to the Task_Relationships table say (1,2) representing the fact that task 1 is related to task 2, that tasks 1 and 2 are in the same job? I'm trying to enforce this through keys and not through code.
drop table if exists dbo.test_jobs
create table dbo.test_jobs (
[Id] int identity(1,1) primary key not null,
[Name] varchar(128) not null
)
drop table if exists dbo.test_tasks
create table dbo.test_tasks (
[Id] int identity(1,1) primary key not null,
[Job_Id] int not null,
[Name] varchar(128) not null
constraint fk_jobs foreign key ([Id]) references dbo.test_jobs(Id)
)
drop table if exists dbo.test_task_relationships
create table dbo.test_task_relationships (
[Id] int identity(1,1) not null,
[From_Task] int not null,
[To_Task] int not null
constraint fk_tasks_from foreign key ([From_Task]) references dbo.test_tasks(Id),
constraint fk_tasks_to foreign key ([To_Task]) references dbo.test_tasks(Id)
)
A reliance on identity columns as primary keys is not helping you here. And it is a logic fault to use an identity column in the relationship table IMO. Surely you do not intend to allow multiple rows to exist in that table with the same values for <from_task, to_task>.
Imagine the child table defined as:
create table dbo.test_tasks (
Job_Id int not null,
Task_Id tinyint not null,
Name varchar(128) not null,
constraint pk_tasks primary key clustered (Job_Id, Task_Id),
constraint fk_jobs foreign key ([Job_Id]) references dbo.test_jobs(Id)
);
Now your relationship table can be transformed into:
create table dbo.test_task_relationships (
From_Job int not null,
From_Task tinyint not null,
To_Job int not null,
To_Task tinyint not null
);
I'll leave it to you to complete the DDL but that should make your goal trivial.
You can declare a superkey in the Task table that includes the Job_Id column as well as columns from an existing key.
create table dbo.test_tasks (
[Id] int identity(1,1) primary key not null,
[Job_Id] int not null,
[Name] varchar(128) not null
constraint fk_jobs foreign key ([Id]) references dbo.test_jobs(Id),
constraint UQ_Tasks_WithJob UNIQUE (Id, Job_Id)
)
You can then add the Job_Id column to the relationships table and include it in both foreign key constraints:
create table dbo.test_task_relationships (
[Id] int identity(1,1) not null,
[From_Task] int not null,
Job_Id int not null,
[To_Task] int not null
constraint fk_tasks_from foreign key ([From_Task], Job_Id) references dbo.test_tasks(Id, Job_Id),
constraint fk_tasks_to foreign key ([To_Task], Job_Id) references dbo.test_tasks(Id, Job_Id)
)
There is now no way for the table to contain mismatched tasks. If necessary, wrap this table in a view/trigger if you don't want to expose the presence of the job_id column to applications and to automatically populate it during insert.

beginner sql missing keyword and invalid identifier

CREATE table Book
(
book_title varchar (100) not null ,
book_genre char(60) not null,
Date_of_publish date not null,
user_code char(7) not null ,
book_id char (7) primary key not null ,
constraint writer__id_fk foreign key (writer_id),
constraint publisher__id_fk foreign key (publisher_id)
);
I'm getting
[ORA-00905: missing keyword]
in publisher table
CREATE table publisher
(
publisher_id char (7) primary key not null,
publisher_name char(20) not null,
publisher_number char(10) not null,
publisher_email varchar2(60) not null,
publisher_address varchar2(60) not null,
);
I'm getting
[ORA-00904: : invalid identifier]
The following SQL creates a FOREIGN KEY on the "PersonID" column when the "Orders" table is created:
CREATE TABLE Orders (
OrderID int NOT NULL,
OrderNumber int NOT NULL,
PersonID int,
PRIMARY KEY (OrderID),
FOREIGN KEY (PersonID) REFERENCES Persons(PersonID)
);
Refer this link for more details
https://www.w3schools.com/sql/sql_foreignkey.asp
Hope this helps.
Welcome to the wonderful world of SQL! :-)
General remark:
Please tell us what kind of DBMS you're using. MySQL? SQL Server? Oracle? SQlite? Different systems use different kinds of syntaxes.
First statement:
The problem seems to be in the FOREIGN KEY-portion.
Usually, you'll state something like:
CONSTRAINT [constraint_name] FOREIGN KEY([column_in_this_table]) REFERENCES OTHER_TABLE([column_in_other_table])
edit (added):
The [column_in_this_table] has to exist in your DDL (CREATE TABLE-statement), like so:
CREATE TABLE Book ( book_title ... etc., publisher_id INT, CONSTRAINT FK_publ_id FOREIGN KEY(publisher_id) REFERENCES publisher(publisher_id));
Here, you'll have a 'original' column called 'publisher_id', in the 'publisher'-table. You refer to it from within the 'Book'-table, by first having a 'publisher_id' column in the 'Book'-table (which should have the same DDL as the original column by the way). Next, you'll add a FOREIGN KEY to the 'Book'-table, that is imposed on the Book(publisher_id) column. Note, that you could also name the column in your 'Book'-table differently -- like, say, 'Spongebob' or 'Patrick'. But for future use, you'd like naming conventions that tell what you might expect to find in a column. So you'd name columns for what they contain.
Second statement:
The problem is with the last portion of your statement, where there's a comma after the NOT NULL portion for column publisher_address.
(Part of) your statement:
publisher_address varchar2(60) not null, );
Try replacing that with:
publisher_address VARCHAR2(60) NOT NULL);
edit (note to self):
VARCHAR2 turns out to be a valid datatype in Oracle databases (see:
Oracle documentation)
For your first table, the Foreign Keys do not reference any table. For your second table, I would imagine that comma after your last column isn't helping anything.
So this is the answer.
CREATE table Book
(
book_title varchar (100) not null ,
book_genre char(60) not null,
Date_of_publish date not null,
user_code char(7) not null ,
publisher_id char (7) not null,
writer_id char(7) not null,
book_id char (7) primary key not null ,
CONSTRAINT book_writer_id_fk FOREIGN KEY(writer_id) REFERENCES writer(writer_id),
CONSTRAINT book_publisher_id_fk FOREIGN KEY(publisher_id) REFERENCES publisher(publisher_id)
);
CREATE table publisher
(
publisher_id char (7) primary key not null,
publisher_name char(20) not null,
publisher_number char(10) not null,
publisher_email varchar2(60) not null,
publisher_address varchar2(60) not null
);

Composite Keys and Referential Integrity in T-SQL

Is it possible, in T-SQL, to have a relationship table with a composite key composed of 1 column defining Table Type and another column defining the Id of a row from a table referenced in the Table Type column?
For a shared-email address example:Three different user tables (UserA, UserB, UserC)One UserType Table (UserType)One Email Table (EmailAddress)One Email-User Relationship Table (EmailRelationship)The EmailRelationship Table contains three columns, EmailId, UserTypeId and UserId
Can I have a relationship from each User table to the EmailRelationship table (or some other way?) to maintain referential integrity?
I've tried making all three columns in the EmailRelationship table into primary keys, I've tried making only UserTypeId and UserId primary.
CREATE TABLE [dbo].[UserType](
[Id] [int] IDENTITY(1,1) NOT NULL ,
[Type] [varchar](50) NOT NULL)
insert into [dbo].[UserType]
([Type])
values
('A'),('B'),('C')
CREATE TABLE [dbo].[UserA](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserA]
(UserTypeId,Name)
values
(1,'UserA')
CREATE TABLE [dbo].[UserB](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserB]
(UserTypeId,Name)
values
(2,'UserB')
CREATE TABLE [dbo].[UserC](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserC]
(UserTypeId,Name)
values
(3,'UserC')
CREATE TABLE [dbo].[Email](
[Id] [int] IDENTITY(1,1) NOT NULL,
[EmailAddress] [varchar](50) NOT NULL)
insert into [dbo].[email]
(EmailAddress)
values
('SharedEmail#SharedEmail.com')
CREATE TABLE [dbo].[EmailRelationship](
[EmailId] [int] NOT NULL,
[UserTypeId] [int] NOT NULL,
[UserId] [int] NOT NULL)
insert into [dbo].[EmailRelationship]
(EmailId, UserTypeId, UserId)
values
(1,1,1),(1,2,1),(1,3,1)
No there isn't, a foreign key can refer to one table, and one table only, I can think of three ways you could approach this.
The first is to have 3 columns, one for each user table, each column with a foreign key, and a check constraint to check that at one, and only one of the values is not null
CREATE TABLE dbo.EmailRelationship
(
EmailId INT NOT NULL,
UserTypeId INT NOT NULL,
UserAId INT NULL,
UserBId INT NULL,
UserCId INT NULL,
CONSTRAINT FK_EmailRelationship__UserAID FOREIGN KEY (UserAId)
REFERENCES dbo.UserA (Id),
CONSTRAINT FK_EmailRelationship__UserBID FOREIGN KEY (UserBId)
REFERENCES dbo.UserB (Id),
CONSTRAINT FK_EmailRelationship__UserCID FOREIGN KEY (UserCId)
REFERENCES dbo.UserC (Id),
CONSTRAINT CK_EmailRelationship__ValidUserId CHECK
(CASE WHEN UserTypeID = 1 AND UserAId IS NOT NULL AND ISNULL(UserBId, UserCId) IS NULL THEN 1
WHEN UserTypeID = 2 AND UserBId IS NOT NULL AND ISNULL(UserAId, UserCId) IS NULL THEN 1
WHEN UserTypeID = 3 AND UserCId IS NOT NULL AND ISNULL(UserAId, UserBId) IS NULL THEN 1
ELSE 0
END = 1)
);
Then as a quick example trying to insert a UserAId with a user Type ID of 2 gives you an error:
INSERT EmailRelationship (EmailID, UserTypeID, UserAId)
VALUES (1, 1, 1);
The INSERT statement conflicted with the CHECK constraint "CK_EmailRelationship__ValidUserId".
The second approach is to just have a single user table, and store user type against it, along with any other common attributes
CREATE TABLE dbo.[User]
(
Id INT IDENTITY(1, 1) NOT NULL,
UserTypeID INT NOT NULL,
Name VARCHAR(50) NOT NULL,
CONSTRAINT PK_User__UserID PRIMARY KEY (Id),
CONSTRAINT FK_User__UserTypeID FOREIGN KEY (UserTypeID) REFERENCES dbo.UserType (UserTypeID),
CONSTRAINT UQ_User__Id_UserTypeID UNIQUE (Id, UserTypeID)
);
-- NOTE THE UNIQUE CONSTRAINT, THIS WILL BE USED LATER
Then you can just use a normal foreign key constraint on your email relationship table:
CREATE TABLE dbo.EmailRelationship
(
EmailId INT NOT NULL,
UserId INT NOT NULL,
CONSTRAINT PK_EmailRelationship PRIMARY KEY (EmailID),
CONSTRAINT FK_EmailRelationship__EmailId
FOREIGN KEY (EmailID) REFERENCES dbo.Email (Id),
CONSTRAINT FK_EmailRelationship__UserId
FOREIGN KEY (UserId) REFERENCES dbo.[User] (Id)
);
It is then no longer necessary to store UserTypeId against the email relationship because you can join back to User to get this.
Then, if for whatever reason you do need specific tables for different user types (this is not unheard of), you can create these tables, and enforce referential integrity to the user table:
CREATE TABLE dbo.UserA
(
UserID INT NOT NULL,
UserTypeID AS 1 PERSISTED,
SomeOtherCol VARCHAR(50),
CONSTRAINT PK_UserA__UserID PRIMARY KEY (UserID),
CONSTRAINT FK_UserA__UserID_UserTypeID FOREIGN KEY (UserID, UserTypeID)
REFERENCES dbo.[User] (Id, UserTypeID)
);
The foreign key from UserID and the computed column UserTypeID back to the User table, ensures that you can only enter users in this table where the UserTypeID is 1.
A third option is just to have a separate junction table for each User table:
CREATE TABLE dbo.UserAEmailRelationship
(
EmailId INT NOT NULL,
UserAId INT NOT NULL,
CONSTRAINT PK_UserAEmailRelationship PRIMARY KEY (EmailId, UserAId),
CONSTRAINT FK_UserAEmailRelationship__EmailId FOREIGN KEY (EmailId)
REFERENCES dbo.Email (Id),
CONSTRAINT FK_UserAEmailRelationship__UserAId FOREIGN KEY (UserAId)
REFERENCES dbo.UserA (Id)
);
CREATE TABLE dbo.UserBEmailRelationship
(
EmailId INT NOT NULL,
UserBId INT NOT NULL,
CONSTRAINT PK_UserBEmailRelationship PRIMARY KEY (EmailId, UserBId),
CONSTRAINT FK_UserBEmailRelationship__EmailId FOREIGN KEY (EmailId)
REFERENCES dbo.Email (Id),
CONSTRAINT FK_UserBEmailRelationship__UserBId FOREIGN KEY (UserBId)
REFERENCES dbo.UserB (Id)
);
Each approach has it's merits and drawbacks, so you would need to assess what is best for your scenario.
No it does not work that way. You cannot use a column value as a dynamic reference to different tables.
In general the data design is flawed.
Thanks to #GarethD I created a CHECK constraint that called a scalar-function that would enforce referential integrity (only upon insert, refer to caveat below):
Using my above example:
alter FUNCTION [dbo].[UserTableConstraint](#Id int, #UserTypeId int)
RETURNS int
AS
BEGIN
IF EXISTS (SELECT Id From [dbo].[UserA] WHERE Id = #Id and UserTypeId = #UserTypeId)
return 1
ELSE IF EXISTS (SELECT Id From [dbo].[UserB] WHERE Id = #Id and UserTypeId = #UserTypeId)
return 1
ELSE IF EXISTS (SELECT Id From [dbo].[UserC] WHERE Id = #Id and UserTypeId = #UserTypeId)
return 1
return 0
end;
alter table [dbo].[emailrelationship]
--drop constraint CK_UserType
with CHECK add constraint CK_UserType
CHECK([dbo].[UserTableConstraint](UserId,UserTypeId) = 1)
I am sure there is a not insignificant overhead to a Scalar-function call from within a CONSTRAINT. If the above becomes prohibitive I will report back here, though the tables in question will not have to deal with a large volume of INSERTs.
If there are any other reasons to not do the above, I would like to hear them. Thanks!
Update:
I've tested INSERT and UPDATE with 100k rows (SQL Server 2014, 2.1ghz quadcore w/ 8gb ram):
INSERT takes 2 seconds with out the CONSTRAINT
and 3 seconds with the CHECK CONSTRAINT
Turning on IO and TIME STATISTICS causes the INSERT tests to run in:
1.7 seconds with out the CONSTRAINT
and 10 seconds with the CHECK CONSTRAINT
I left the STATISTICS on for the UPDATE 100k rows test:
just over 1sec with out the CONSTRAINT
and 1.5sec with the CHECK CONSTRAINT
My referenced tables (UserA, UserB, UserC from my example) only contain around 10k rows each, so anybody else looking to implement the above may want to run some additional testing, especially if your referenced tables contain millions of rows.
Caveat:
The above solution may not be suitable for most uses, as the only time referential integrity is checked is during the CHECK CONSTRAINT upon INSERT. Any other operations or modifications of the data needs to take that into account. For example, using the above, if an Email is deleted any related EmailRelationship entries will be pointing to invalid data.

Use a common table with many to many relationship

I have two SQL tables: Job and Employee. I need to compare Job Languages Proficiencies and Employee Languages Proficiencies. A Language Proficiency is composed by a Language and a Language Level.
create table dbo.EmployeeLanguageProficiency (
EmployeeId int not null,
LanguageProficiencyId int not null,
constraint PK_ELP primary key clustered (EmployeeId, LanguageProficiencyId)
)
create table dbo.JobLanguageProficiency (
JobId int not null,
LanguageProficiencyId int not null,
constraint PK_JLP primary key clustered (JobId, LanguageProficiencyId)
)
create table dbo.LanguageProficiency (
Id int identity not null
constraint PK_LanguageProficiency_Id primary key clustered (Id),
LanguageCode nvarchar (4) not null,
LanguageLevelId int not null,
constraint UQ_LP unique (LanguageCode, LanguageLevelId)
)
create table dbo.LanguageLevel (
Id int identity not null
constraint PK_LanguageLevel_Id primary key clustered (Id),
Name nvarchar (80) not null
constraint UQ_LanguageLevel_Name unique (Name)
)
create table dbo.[Language]
(
Code nvarchar (4) not null
constraint PK_Language_Code primary key clustered (Code),
Name nvarchar (80) not null
)
My question is about LanguageProficiency table. I added an Id has PK but I am not sure this is the best option.
What do you think about this scheme?
Your constraint of EmployeeId, LanguageProficiencyId allows an employee to have more than one proficiency per language. This sounds counterintuitive.
This would be cleaner, as it allows only one entry per language:
create table dbo.EmployeeLanguageProficiency (
EmployeeId int not null,
LanguageId int not null,
LanguageLevelId int not null,
constraint PK_ELP primary key clustered (EmployeeId, LanguageId)
)
I don't see the point of table LanguageProficiency at the moment.
Same applies to the Job of course. Unless you would like to allow a "range" of proficiencies. But assuming that "too high proficiency" does not hurt, it can easilly be defined through a >= statement in our queries.
Rgds

SQL Syntax error with foreign keys

now i have table place CREATE TABLE [dbo].[Place] (
[Place_Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (50) NOT NULL,
[Building_Date] DATE NULL,
[Longitude] VARCHAR (50) NULL,
[Latitude] VARCHAR (50) NULL,
[Location] VARCHAR (50) NOT NULL,
PRIMARY KEY CLUSTERED ([Place_Id] ASC)
); , and table Citeria CREATE TABLE [dbo].[Criteria] (
[Criteria] VARCHAR (50) NOT NULL,
[Place_Id] INT NOT NULL, PRIMARY KEY CLUSTERED ([Criteria], [Place_Id]), CONSTRAINT [FK_Criteria_ToTable] FOREIGN KEY (Place_Id) REFERENCES Place(Place_Id)
);and The referenced table '[dbo].[Criteria]' contains no primary or candidate keys that match the referencing column list in the foreign key. If the referenced column is a computed column, it should be persisted.
.
The error is correct. Subqueries are not allowed in check constraints.
But, you already have a foreign key reference between user_name and likes(user_name), so this condition is already in place. The only thing is would really be checking is that user_name is not NULL, but that is already true by the definition of the column.
Now, there are other issues. Your foreign keys should be to primary keys or unique keys in other tables. I think this is your intention:
CREATE TABLE [dbo].Normal_Upload
(
[User_Name] VARCHAR(50) NOT NULL ,
[Place_Id] INT NOT NULL,
[Image] IMAGE NOT NULL,
CONSTRAINT [FK_Normal_Upload] FOREIGN KEY (User_Name) REFERENCES Member(User_Name),
CONSTRAINT [FK_Normal_Upload_1] FOREIGN KEY (Place_Id) REFERENCES Place(Place_Id),
CONSTRAINT [FK_Normal_Upload_2] FOREIGN KEY (User_Name, Place_Id) REFERENCES Likes(User_Name, Place_Id)
);
As a note on naming. I think the primary keys of tables should include the table name. So, consider Member_Name rather than User_Name for the Member table.
It wrong db design, never use varchar or character type column in reference key or primary-key(try to avoid as much as possible).
For you solution, create a column "useid" with int datatype and give pk to it. and update the following table
CREATE TABLE [dbo].[Likes] (
[User_id] VARCHAR (50) Identity (1,1),
[User_Name] VARCHAR (50) NOT NULL,
[Place_Id] INT NOT NULL,
CONSTRAINT [PK_Likes] PRIMARY KEY CLUSTERED ([User_id] ASC, [Place_Id] ASC),
CONSTRAINT [FK_Likes_ToTable] FOREIGN KEY ([User_Name]) REFERENCES Normal ([User_Name]),
CONSTRAINT [FK_Likes_ToTable_1] FOREIGN KEY ([Place_Id]) REFERENCES [dbo].[Place] ([Place_Id]),
);