Sql constraint method returns a false value - sql

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.

Related

Trouble using an equal sign in SQL trigger

This is my table:
CREATE TABLE [dbo].[tblVisitors] (
[Id] BIGINT IDENTITY (1, 1) NOT NULL,
[IP] NVARCHAR (100) NOT NULL,
[ProfileId] INT NULL,
[DateVisit] DATE NOT NULL,
[TimeVisit] TIME (0) NOT NULL,
[Browser] NVARCHAR (50) NOT NULL,
[UserOS] NVARCHAR (500) NOT NULL,
CONSTRAINT [PK_tblVisitors] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_tblVisitors_tblProfile] FOREIGN KEY ([ProfileId]) REFERENCES [dbo].[tblProfile] ([Id]) ON DELETE SET NULL
);
I wrote a trigger to avoid redundancy:
CREATE TRIGGER [dbo].[Trigger_tblVisitors_OnInsert]
ON [dbo].[tblVisitors]
INSTEAD OF INSERT
AS
BEGIN
SET NoCount ON;
DECLARE #C INT;
SELECT *
INTO #TEMP
FROM inserted A
WHERE
NOT EXISTS (SELECT *
FROM tblVisitors B
WHERE (A.IP = B.IP)
AND (A.DateVisit = B.DateVisit)
AND (A.ProfileId = B.ProfileId));
IF (SELECT COUNT(*) FROM #TEMP) = 0
BEGIN
PRINT 'DUPLICATE RECORD DETECTED';
ROLLBACK TRANSACTION;
RETURN;
END
INSERT INTO tblVisitors (IP, ProfileId, DateVisit, TimeVisit, Browser, UserOS)
SELECT IP, ProfileId, DateVisit, TimeVisit, Browser, UserOS
FROM #TEMP;
END
But as this part of the code does not work, redundancy occurs:
(A.ProfileId = B.ProfileId)
Because after deleting this section, the operation is performed correctly. But this condition must be checked.
Using my psychic skills, I suspect that you have ProfileId values that are null, and in SQL the expression null = null is not true, but your logic requires it to be true.
Try this:
AND (A.ProfileId = B.ProfileId OR (A.ProfileId IS NULL AND B.ProfileId IS NULL))

SQL Server nested triggers

I have three tables:
CREATE TABLE Rents
(
RentID INT IDENTITY NOT NULL PRIMARY KEY,
StartDate SMALLDATETIME,
EndDate SMALLDATETIME,
Price MONEY,
RealEstateID INT UNIQUE NOT NULL,
DealMadeByEmployeeID INT NOT NULL,
CONSTRAINT CHK_Rents CHECK (Price > 0 AND EndDate > StartDate),
CONSTRAINT FK_Rents_EstatesBasicInfo
FOREIGN KEY (RealEstateID) REFERENCES EstatesBasicInfo(RealEstateID),
CONSTRAINT FK_Rents_Employees
FOREIGN KEY (DealMadeByEmployeeID) REFERENCES Employees(EmployeeID)
);
CREATE TABLE Purchases
(
PurchaseID INT IDENTITY NOT NULL PRIMARY KEY,
DateBought SMALLDATETIME,
Price MONEY CHECK (Price>0),
RealEstateID INT UNIQUE NOT NULL,
DealMadeByEmployeeID INT NOT NULL,
CONSTRAINT FK_Purchases_EstatesBasicInfo
FOREIGN KEY (RealEstateID) REFERENCES EstatesBasicInfo(RealEstateID),
CONSTRAINT FK_Purchases_Employees
FOREIGN KEY (DealMadeByEmployeeID) REFERENCES Employees(EmployeeID)
);
CREATE TABLE EmployeesSalary
(
EmployeeID INT NOT NULL PRIMARY KEY,
CurrentSalary MONEY DEFAULT 0,-- на процент
MonthlySalesMade INT DEFAULT 0,
MonthlyRentsMade INT DEFAULT 0,
CONSTRAINT FK_EmployeesSalary_Employees
FOREIGN KEY (EmployeeID) REFERENCES Employees(EmployeeID),
CONSTRAINT CHK_EmployeesSalary
CHECK (CurrentSalary >= 0 AND MonthlySalesMade >= 0 AND MonthlyRentsMade >= 0)
);
Each of them has a trigger
CREATE TRIGGER tr_EmployeesSalaryPurchasesUpdate --при INSERT в Purchases таблицата
ON Purchases
AFTER INSERT
AS
BEGIN
UPDATE EmployeesSalary
SET EmployeesSalary.MonthlySalesMade = EmployeesSalary.MonthlySalesMade + 1
WHERE EmployeesSalary.EmployeeID IN (SELECT inserted.DealMadeByEmployeeID
FROM inserted
WHERE DateBought IS NOT NULL)
END
--Update на MonthlyRentsMade
GO
CREATE TRIGGER tr_EmployeesSalaryRentsUpdate --при INSERT в Rents таблицата
ON Rents
AFTER INSERT
AS
BEGIN
UPDATE EmployeesSalary
SET MonthlyRentsMade = MonthlyRentsMade + 1
WHERE EmployeesSalary.EmployeeID IN (SELECT inserted.DealMadeByEmployeeID
FROM inserted
WHERE StartDate IS NOT NULL)
END
The problem comes when I want to add a trigger to EmployeesSalary:
CREATE TRIGGER tr_EmployeesSalaryCurrentSalary
ON EmployeesSalary
AFTER INSERT
AS
BEGIN
UPDATE EmployeesSalary
SET CurrentSalary = CurrentSalary + ((MonthlySalesMade + MonthlyRentsMade) * 200)
WHERE EmployeeID IN (SELECT i.EmployeeID
FROM inserted AS i);
END
I want when I get an insert in the EmployeesSalary (by the other two triggers), the CurrentSalary to be updated (depending on the EmployeeID). The trigger cause any errors but it doesn't work. (I think this is called nested triggers not sure...) Where is my mistake?
I would prefer to use a computed column instead of a trigger for something like this. Here is an example. I also added ISNULL around the two columns in case you have a NULL. Otherwise the computation will always be NULL.
alter table EmployeesSalary
add ComputedSalary as CurrentSalary + ((isnull(MonthlySalesMade, 0) + isnull(MonthlyRentsMade, 0)) * 200) persisted
Since this is an after-insert trigger (recursion won't occur on update table). I think where you are making a mistake is using NULL values in calculations so I would try this:
CREATE TRIGGER tr_EmployeesSalaryCurrentSalary
ON EmployeesSalary
AFTER INSERT
AS
BEGIN
UPDATE es
SET CurrentSalary = coalesce(i.CurrentSalary, 0) + ((coalesce(i.MonthlySalesMade, 0) + coalesce(i.MonthlyRentsMade, 0)) * 200)
from EmployeesSalary as es
inner join inserted AS i
on i.EmployeeID = es.EmployeeID
END
Also, I don't like IN statements so I'm joining instead.

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;

Adding CHECK constraint conflicts with IS NULL

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