One Active Record per ID - sql-server-2012

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;

Related

Stored procedure for login not working as intended

I have 3 tables, users, Roles, and User_Roles.
CREATE TABLE [Web].[Users]
(
[Employee_ID] [Nvarchar] (10) NOT NULL PRIMARY KEY,
[Username] [nvarchar] (25) NOT NULL,
)
GO
CREATE TABLE [Web].[Roles]
(
[Role_ID] [Int] NOT NULL IDENTITY PRIMARY KEY,
[Roles] [nvarchar] (25) NOT NULL,
)
GO
CREATE TABLE [Web].[User_Roles]
(
[Employee_ID] [Nvarchar](10) NOT NULL,
[Role_ID] [int] NOT NULL,
CONSTRAINT FK_Employee_ID
FOREIGN KEY (Employee_ID) REFERENCES [Web].[Users] (Employee_ID)
ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT FK_Role_ID
FOREIGN KEY (Role_ID) REFERENCES [Web].[Roles] (Role_ID),
CONSTRAINT pk_User_Roles PRIMARY KEY (Employee_ID, Role_ID)
);
The way it works is, when you login, it checks your Username against the Employee_ID in Users table, then goes to the User_Roles table and matches the Employee_ID it got from the Users table to the User_Roles table. Then it gets the Role_ID from the User_Roles table and matches the Role_ID to the roles in the Roles table.
I'm trying to write a stored procedure that does the checks and so far I've gotten close, but I feel I'm missing something. Hoping I can get help with it. This is what I have so far.
CREATE PROCEDURE [Web].[Get_User_Roles]
#Username NVARCHAR(25)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Employee_ID Nvarchar, #Role_ID INT
SELECT #Employee_ID = #Employee_ID, #Role_ID = #Role_ID
FROM [Web].[Users]
WHERE #Employee_ID = #Employee_ID AND #Role_ID = #Role_ID
IF #Employee_ID IS NOT NULL
BEGIN
IF NOT EXISTS (SELECT #Employee_ID FROM [Web].Users
WHERE #Username = Username)
BEGIN
SELECT
#Employee_ID [Employee_ID],
(SELECT #Role_ID FROM User_Roles
WHERE #Role_ID = #Role_ID) [Roles]-- User Valid
END
ELSE
BEGIN
SELECT -2 [#Username], '' [Roles]--User not activated
END
END
ELSE
BEGIN
SELECT -1 [#Username], '' [Roles]-- User invalid
END
END
Any help to improve this would be appreciated. I put the tables code above so that it's available. My final outcome I shall ask again in this section as well as the middle. The way it works is, when you login, it checks your Username against the Employee_ID in Users Table, then goes to the User_Roles table and matches the Employee_ID it got from the Users table to the User_Roles table. Then it gets the Role_ID from the User_Roles table and matches the Role_ID to the Roles in the Roles table. I'm trying to make a stored procedure that does the checks and so far I've gotten close, but I feel I'm missing something. Hoping I can get help with it. This is what I have so far.
First of all, this part is not going to work at all:
select #Employee_ID = #Employee_ID, #Role_ID = #Role_ID
from [Web].[Users] WHERE #Employee_ID = #Employee_ID and #Role_ID = #Role_ID
You are comparing variables with themselves in the WHERE Statement, you are assigning variables to each other, and you have no Role_ID in the [Web].[Users] table. Try the following
SELECT Employee_ID, Roles
FROM [Web].[Users] A
INNER JOIN [Web].[User_Roles] B
ON A.[Employee_ID] = B.[Employee_ID]
INNER JOIN [Web].[Roles] C
ON b.[Role_ID] = C.[Role_ID]
WHERE [Username] = #Username
This code will return the Employee_id and Roles if you have a user with that username, or an empty data set otherwise.
Still not very clear what you want as output but I think it would be something like this. This will return -2 in the UserStatus column if there isn't a role for the user. Otherwise it will return 1 in that column and the Role. You should just check if there are rows returned when you call this. If it returns no rows then the user doesn't exist.
select UserStatus = case when ur.Role_ID is null then -2 else 1 end
, r.Roles
from Web.Users u
left join Web.User_Roles ur on ur.Employee_ID = u.Employee_ID
left join Web.Roles r on r.Role_ID = ur.Role_ID
where u.Username = #Username

Cascade update sql trigger

I have two tables in two different databases Lab15DB_1 and Lab15DB_2.
The parent table is Author and the child table is Book.
Here they are:
CREATE TABLE Author
(
AuthorId INT NOT NULL PRIMARY KEY,
AuthorName NVARCHAR(50) NOT NULL,
AuthorSurname NVARCHAR(50) NOT NULL,
)
CREATE TABLE Book
(
BookId INT NOT NULL PRIMARY KEY,
BookName NVARCHAR(50) NOT NULL,
BookType NVARCHAR(50) NOT NULL CHECK(BookType IN ('Science', 'Hobby', 'Cooking', 'Fishing', 'Nature', 'Favorites')),
PublishYear INT NOT NULL,
AuthorId INT NOT NULL,
Price MONEY CHECK (PRICE > 0.0)
)
I want to create cascade update via triggers.
Here is an attempt to create trigger for cascade update on the Author table:
CREATE TRIGGER UpdateAuthorTrigger
ON Author
FOR UPDATE
AS
DECLARE #intRowCount int
SELECT #intRowCount = ##ROWCOUNT
IF #intRowCount > 1
BEGIN
IF UPDATE(AuthorId)
BEGIN
UPDATE Author
SET AuthorId = AuthorId
/* some code for update Book*/
END
END
ELSE
IF #intRowCount = 1
BEGIN
IF UPDATE(AuthorId)
BEGIN
UPDATE Author
SET Author.AuthorId = (SELECT AuthorId FROM INSERTED)
FROM Author
INNER JOIN DELETED ON Author.AuthorId = DELETED.AuthorId
UPDATE Lab15DB_1.dbo.Book
SET Lab15DB_1.dbo.Book.AuthorId = (SELECT AuthorId FROM INSERTED)
FROM Lab15DB_1.dbo.Book
INNER JOIN DELETED ON Lab15DB_1.dbo.Book.AuthorId = DELETED.AuthorId
END
END
GO
There is no problem with case #intRowCount = 1, but I have difficulties with case #intRowCount > 1.
Could you please give me any advice?

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))

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

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.