Adding CHECK constraint conflicts with IS NULL - sql

I got a problem when I try to add a constraint to one of my tables. I want to check in a function so that a state is true and then returns 1 or 0 depending on if it is true. But in the function I check if a value in a column is NULL and that causes the error
The ALTER TABLE statement conflicted with the CHECK constraint "chk_StateFinished". The conflict occurred in database "databaseName", table "dbo.Participation".
the Function looks like this
CREATE FUNCTION CheckStateFinished(#StudentID varchar(10), #CourseID varchar(10), #CoursePeriod varchar(10),
#SchoolYear int, #State varchar(15)) RETURNS int
AS BEGIN
DECLARE #Grade varchar(1)
SELECT #Grade = Grade FROM Participation WHERE StudentID = #StudentID AND CourseID = #CourseID AND CoursePeriod = #CoursePeriod AND SchoolYear = #SchoolYear
RETURN CASE WHEN #State = 'Avslutad' AND #Grade = 'U' OR #Grade IS NULL THEN 0
ELSE 1
END
END
And the Add Check Constraint looks like this:
ALTER TABLE Participation ADD CONSTRAINT chk_StateFinished CHECK (dbo.CheckStateFinished(StudentID, CourseID, CoursePeriod, SchoolYear, _State) = 1)
What should I do instead of IS NULL in the Function or should i do something else?

The issue is not in function CheckStateFinished but with existing data in table Participation on which CHECK CONSTRAINT is to be added. When we add Check constraint to an existing table using Alter table command by default it applies to both existing data and any new data.
There might be some row in table Participation where for given StudentID, CourseID, CoursePeriod, SchoolYear, _State parameters function is evaluating to 0 and hence Check constraint is getting failed.
In such a case use WITH NOCHECK option so that Check constraint applies only to new data.
create table Participation (Grade varchar(1),StudentID varchar(10), CourseID varchar(10), CoursePeriod varchar(10), SchoolYear int, [State] varchar(15))
insert into Participation values ('A','Student1','Course1','CourseP1',2013,'Avslutad')
-- for this row check constraint will work fine.
insert into Participation values ('U','Student2','Course1','CourseP1',2013,'Avslutad') -- for this row check constraint will fail.
insert into Participation values (NULL,'Student3','Course1','CourseP1',2013,'Avslutad')
-- for this row check constraint will fail.
insert into Participation values ('U','Student4','Course1','CourseP1',2013,'XYZ')
-- for this row check constraint will work fine.
--insert into Participation values ('A','Student5','Course1','CourseP1',2013,'XYZ')
Go
CREATE FUNCTION CheckStateFinished(#StudentID varchar(10), #CourseID varchar(10), #CoursePeriod varchar(10),
#SchoolYear int, #State varchar(15)) RETURNS int
AS BEGIN
DECLARE #Grade varchar(1)
SELECT #Grade = Grade FROM Participation WHERE StudentID = #StudentID AND CourseID = #CourseID AND CoursePeriod = #CoursePeriod AND SchoolYear = #SchoolYear
RETURN CASE WHEN #State = 'Avslutad' AND #Grade = 'U' OR #Grade IS NULL THEN 0
ELSE 1
END
END
Go
ALTER TABLE Participation WITH NOCHECK -- add this and your constraint will work.
ADD CONSTRAINT chk_StateFinished CHECK (dbo.CheckStateFinished('Student3','Course1','CourseP1',2013,'Avslutad') = 1)
Go

Related

trigger for not inserting members doesnt work

I have this table
CREATE TABLE members
(
member_id INT PRIMARY KEY NOT NULL,
first_name VARCHAR(20),
last_name VARCHAR(20),
web_page VARCHAR(200),
e_mail VARCHAR(200),
cv VARCHAR(800),
dep_id INT,
teacher_id INT
);
and I want to create a trigger that if someone wants to insert a member which has a dep_id of 1 or 2 or 3.
And the teacher_id is different than NULL (as the teacher_id column is filled with either NULL or an id of another member)
I came up with this
CREATE TRIGGER employee_insup1
ON members
FOR INSERT, UPDATE
AS
DECLARE #dep_id INT, #teacher_id INT
SELECT #dep_id = i.dep_id, #teacher_id = i.teacher_id
FROM inserted i
IF ((#dep_id = 1) AND (#teacher_id != NULL))
BEGIN
RAISERROR('Teacher_id expects NULL',16,1)
ROLLBACK TRANSACTION
END
but after all if I try to insert a member with dep_id 1 and teacher_id 7(for example) it will be registered
You don't need a trigger for this. A check constraint is sufficient:
alter table members add constraint chk_members_dep_teacher
check (dep_id not in (1, 2, 3) or teacher_id is not null);
Specifically, this ensures that when dep_id is in one of those departments, then the teacher_id is not null. You might find the logic easier to follow as:
alter table members add constraint chk_members_dep_teacher
check (not (dep_id nt in (1, 2, 3) and teacher_id is null) );

Syntax for CHECKing inserted value against another table's

I'm adding a table in my Database:
***Enrollments***
CourseID
Student ID
EnrollmentDateTime
I already have a table:
***Courses***
CourseID
StartDate
When someone's inserted data into the Enrollments table, I want to check whether the enrollment date is lesser than/ before the start date; I know what needs to be done, but am lacking the syntax on how to do so. So far, I have the following, but am at a loss on how to proceed.
Create Table Enrollments(
StudentID int NOT NULL Foreign Key REFERENCES dbo.Students(StudentID),
CourseID int NOT NULL Foreign Key REFERENCES dbo.Courses(CourseID),
EnrollmentDateTime Datetime NOT NULL CHECK (dbo.fCheckDate(EnrollmentDateTime, CourseID) = 1))
...
create function dbo.fCheckDate (#StartDate Datetime, #CourseID int)
Returns bit
AS
Begin
Return(SELECT E.StartDate
FROM dbo.Enrollments E
WHERE #CourseID = E.CourseID
End
go
Such function-based checks are quite expensive. But, you need a function to return the course date:
create function dbo.getStartDate (#CourseID int)
Returns datetime
AS
Begin
Return (SELECT c.StartDate
FROM dbo.Courses c
WHERE c.CourseID = #CourseID
);
End;
Create Table Enrollments (
StudentID int NOT NULL Foreign Key REFERENCES dbo.Students(StudentID),
CourseID int NOT NULL Foreign Key REFERENCES dbo.Courses(CourseID),
EnrollmentDateTime Datetime NOT NULL CHECK (EnrollmentDateTime < getStartDate(CourseID))
);

How to trigger a table to change the value of another table column

I've created three tables.
CREATE TABLE Clients
(
ClientID INT IDENTITY(1,1) PRIMARY KEY,
First_Name VARCHAR(50) NOT NULL,
Last_Name VARCHAR(50) NOT NULL,
)
CREATE TABLE Reservation
(
ReservationID INT IDENTITY(1,1) PRIMARY KEY,
ClientID INT FOREIGN KEY (ClientID) REFERENCES Clients(ClientID),
Reservation_paid VARCHAR(3) DEFAULT 'NO',
)
CREATE TABLE Payment
(
Payment_ID INT IDENTITY(1,1) PRIMARY KEY,
ClientID INT FOREIGN KEY (ClientID) REFERENCES Clients(ClientID),
ReservationID INT FOREIGN KEY (ReservationID) REFERENCES Reservation(ReservationID),
)
I would like to change the value of the column Reservation_paid to YES at the Reservation table whenever the Client does pay the reservation, and i want to do it automatically with trigger.
Example: If the ClientID at the Reservation table exists at the Payment table automatically the value of the Reservation_paid will set to YES.
Thank you in advance.
CREATE TRIGGER trgAfterInsert ON [dbo].[Payment]
FOR INSERT
AS
declare #ClientID int;
select #ClientID =i.ClientID from inserted i;
if update(ClientID)
UPDATE Reservation set Reservation_paid='Yes' WHERE
ClientID=#ClientID;
--PRINT 'AFTER INSERT trigger fired.'
After Insert Trigger should do something like this
UPDATE R
SET Reservation_paid = 'Yes'
FROM reservation R
WHERE EXISTS (SELECT 1
FROM INSERTED I
WHERE I.clientid = R.clientid
AND I.reservationid = R.reservationid)
CREATE TRIGGER trgAfterInsert ON [dbo].[Payment]
FOR INSERT
AS
declare #ClientID int;
select #ClientID =i.ClientID from inserted i;
insert into Reservation(ClientID,Reservation_paid)
values(#ClientID,'Yes');
--PRINT 'AFTER INSERT trigger fired.'
GO
Write a trigger that will work on table Reservation after any insert or update on ClientId column of table Payment. Then match the ClientID with ClientID column of Reservation table and update the corresponding Reservation_paid to YES.
Edit:
The trigger will be like this
CREATE TRIGGER `UpdateReservation_paid` AFTER INSERT OR UPDATE ON `Payment`
FOR EACH ROW BEGIN
AS
begin
update Reservation
SET Reservation_paid='YES'
Where NEW.ClientID = Reservation.ClientID
and NEW.ReservationID = Reservation.ReservationID
end

One Active Record per ID

The address table is shared for both companies and contacts. I have a constraint that prevents the user from adding a record with both a Company ID and a Contact ID. I am trying to add another constraint to have only 1 active (ACTIVE FLAG = 'TRUE') address per Company ID or Contact ID. But I want to be able to have unlimited inactive (ACTIVE FLAG = 'FALSE').
ALTER TABLE [dbo].[ADDRESSES] WITH CHECK ADD CONSTRAINT [chk_ONLY_ONE_ACTIVE_ADDRESS] CHECK (([COMPANY_ID] IS NOT NULL AND [CONTACT_ID] IS NULL AND [ACTIVE] = 'TRUE' OR [COMPANY_ID] IS NULL AND [CONTACT_ID] IS NOT NULL AND [ACTIVE] = 'TRUE' OR [COMPANY_ID] IS NULL AND [CONTACT_ID] IS NOT NULL AND [ACTIVE] = 'FALSE' OR [COMPANY_ID] IS NOT NULL AND [CONTACT_ID] IS NULL AND [ACTIVE] = 'FALSE'))
GO
Where am I missing it?
Thanks
jlimited
You are adding the constraint 'active=true' and 'active=false' for both combinations of contact id and company id so it's a tautology, you may as well remove this constraint.
I believe the only way to enforce the rule 'only one active flag and 0 to many inactive' is by triggers.
Thanks for the reply Jayvee. Here is the solution that I figured out.
I first created two functions...
One
CREATE FUNCTION [dbo].[fnCheckForActiveContactAddress](
#id int
)
RETURNS INT
AS
BEGIN
DECLARE #result INT
IF #id IS NOT NULL
SET #result = ISNULL((select count(*) from dbo.Addresses where CONTACT_ID = #ID AND ACTIVE = 'TRUE'),0)
ELSE
SET #result = 1
RETURN #result
END
GO
Two
CREATE FUNCTION [dbo].[fnCheckForActiveCompanyAddress](
#id int
)
RETURNS INT
AS
BEGIN
DECLARE #result INT
If #id IS NOT NULL
SET #result = ISNULL((select count(*) from dbo.Addresses where COMPANY_ID = #ID AND ACTIVE = 'TRUE'),0)
ELSE
SET #result = 1
RETURN #result
END
GO
Then I added the following constraints...
ALTER TABLE [dbo].[ADDRESSES] WITH CHECK ADD CONSTRAINT [chk_COMPANY_OR_CONTACT] CHECK (([COMPANY_ID] IS NOT NULL AND [CONTACT_ID] IS NULL OR [COMPANY_ID] IS NULL AND [CONTACT_ID] IS NOT NULL))
GO
ALTER TABLE [dbo].[ADDRESSES] WITH NOCHECK ADD CONSTRAINT [CHK_ADDRESSES_ONLY_ONE_ACTIVE_CONTACT] CHECK (([dbo].[fnCheckForActiveContactAddress]([CONTACT_ID])=(1)))
GO
ALTER TABLE [dbo].[ADDRESSES] WITH NOCHECK ADD CONSTRAINT [CHK_ADDRESSES_ONLY_ONE_ACTIVE_COMPANY] CHECK (([dbo].[fnCheckForActiveCompanyAddress]([COMPANY_ID])=(1)))
GO
This solution seems to work well.
Any thoughts on improving it?
jlimited
If I understand your requirements, this should work (if -1 is not a valid CompanyID or ContactID):
create table T (
CompanyID int,
ContactID int,
BothIDs as
CASE WHEN CompanyID IS NOT NULL and ContactID IS NOT NULL
THEN 1 ELSE 0 END PERSISTED
check (BothIDs = 0),
ActiveFlag varchar(5) check (ActiveFlag in ('TRUE','FALSE')),
ActiveCheck as
CASE WHEN ActiveFlag='TRUE' then -1 ELSE COALESCE(CompanyID,ContactID) END,
unique (ActiveCheck)
);
insert into T values
(1,NULL,'FALSE'),
(NULL,2,'FALSE'),
(3,NULL,'TRUE'),
(4,NULL,'FALSE');
GO
UPDATE T SET
ActiveFlag = 'TRUE'
WHERE CompanyID = 4;

Sql constraint method returns a false value

I have a problem with a constraint method in sql.
This is my table
CREATE TABLE [relations].[CompoundKey_Contacts](
[compoundId] [varchar](32) NOT NULL,
[companyId] [varchar](32) NULL,
[personId] [varchar](32) NULL,
[contactInfoId] [varchar](32) NOT NULL)
When you add a row to this table it should check that this combination of person and company does not already exist in the table. For this I use a constraint function
Constraint
ALTER TABLE [relations].[CompoundKey_Contacts] WITH NOCHECK ADD CONSTRAINT [CK_CompoundKey_Contacts] CHECK (([relations].[doesThisCompoundKeyExist]([personId],[companyId])='NO'))
GO
ALTER TABLE [relations].[CompoundKey_Contacts] CHECK CONSTRAINT [CK_CompoundKey_Contacts]
GO
Function
CREATE function [relations].[doesThisCompoundKeyExist](
#personId varchar(32),
#companyId varchar(32)
)
returns varchar(3)
as
begin
declare #exists varchar(32)
if(#companyId is null and #personId is null)
set #exists = 'YES'
else if(#personId is null)
if exists(select compoundId from relations.CompoundKey_Contacts where personId is null AND companyId = #companyId)
set #exists = 'YES' 'This is where to code enters, but it should come to the else and return 'NO'
else
set #exists = 'NO'
else if(#companyId is null)
if exists(select compoundId from relations.CompoundKey_Contacts where personId = #personId AND companyId is null)
set #exists = 'YES'
else
set #exists = 'NO'
else if exists(
select compoundId from relations.CompoundKey_Contacts where personId = #personId AND companyId = #companyId
)
set #exists = 'YES'
else
set #exists = 'NO'
return #exists
end;
My insert statement that fails
insert into relations.CompoundKey_Contacts (companyId, contactInfoId, personId, compoundId) values ('COM-000015945', 'INF-000144406', null, 'CPK-000000067');
The problem is this. When I run an insert on the table with a unique insert it still fails. I have of course checked that it rely is unique with a select statement. And here comes the funny part. When I do debug it and check where it fails and break out that code part and run it free without being in a function it behaves as it should so the following code works if its not run in a function
if exists(select compoundId from relations.CompoundKey_Contacts where personId is null AND companyId = 'COM-000015945')
print 'YES'
else
print 'NO' 'Returns NO as it should.
This is the error msg I get
The INSERT statement conflicted with the CHECK constraint "CK_CompoundKey_Contacts". The conflict occurred in database "domas", table "relations.CompoundKey_Contacts".
The statement has been terminated.
I run this on both a Sql Server 2012 and Sql Server 'DENALI' CTP3
Using a UDF in a check constraint won't work reliably as you can see
Use a unique constraint on a computed column if you require extra logic
ALTER TABLE CompoundKey_Contacts
ADD CompoundKey AS ISNULL(personID, 'NOPERSONID') + ISNULL(companyId, 'NOCOMPANYID');
ALTER TABLE CompoundKey_Contacts WITH CHECK
ADD CONSTRAINT UQ_CompoundKey_Contacts_CompoundKey UNIQUE (CompoundKey);
Or a simple unique constraint
ALTER TABLE CompoundKey_Contacts WITH CHECK
ADD CONSTRAINT UQ_CompoundKey_OtherUnique UNIQUE (personID, companyId);
Create a unique constraint or unique index on personId,companyId.
Don't try using a check constraint with a UDF for this as it is inefficient and difficult to get correct anyway.