Check Constraint not firing correctly - sql-server

I'm pretty sure I am doing something wrong, as this is my first check constraint, but I can't understand why it's not working. I need to check that a date range doesn't overlap.
ALTER FUNCTION fn_DateOverlaps (#StartDate DATE, #EndDate DATE, #ProjectID INT)
RETURNS BIT
AS
BEGIN
DECLARE #Ret BIT
SET #Ret = 1
IF NOT EXISTS(
SELECT * FROM project_sprint
WHERE ((#StartDate >= StartDate AND #EndDate <= EndDate)
OR (#StartDate <= StartDate AND #EndDate >= EndDate))
AND ProjectId = #ProjectId
)
BEGIN
SET #Ret = 0
END
RETURN #Ret
END
GO
I then apply this to my table:
ALTER TABLE Project_Sprint WITH CHECK ADD CONSTRAINT ck_DateOverlaps CHECK (dbo.fn_DateOverlaps([StartDate], [EndDate], [ProjectId])=1)
GO
When I test the function, I get a good result:
SELECT dbo.fn_DateOverlaps('2013-06-10', '2013-06-13', 1)
But then when I apply the same date range and project ID to my table, it allows the insert. It should fail it.
What am I doing wrong?

If you change SELECT * FROM project_sprint to SELECT * FROM dbo.project_sprint the function will correctly evaluate, but you won't get the desired behavior, since the value you are inserting or editing will lead to an unwanted find.
To prevent this you will have to add an additional ID field to except the row you want to edit/insert for the check.
Create Table Project_Sprint(ID int IDENTITY(1,1) NOT NULL,ProjectID int,StartDate DateTime,EndDate DateTime)
go
Alter FUNCTION fn_DateOverlaps (#ID int,#StartDate DATE, #EndDate DATE, #ProjectID INT)
RETURNS BIT
AS
BEGIN
DECLARE #Ret BIT
SET #Ret = 0
IF NOT EXISTS(
SELECT * FROM dbo.project_sprint
WHERE
#ID<>ID AND
((#StartDate >= StartDate AND #EndDate <= EndDate)
OR (#StartDate <= StartDate AND #EndDate >= EndDate))
AND ProjectId = #ProjectId
)
BEGIN
SET #Ret = 1
END
RETURN #Ret
END
GO
ALTER TABLE Project_Sprint ADD CONSTRAINT ck_DateOverlaps CHECK (dbo.fn_DateOverlaps([ID],[StartDate], [EndDate], [ProjectId])=1)

Related

How To add Extra Hours in One particular Date using store procedure

ALTER PROCEDURE [dbo].[USP_HR_SelectLast_Attendance_Master]
AS
BEGIN
DECLARE #EmployeeId varchar(50),
#CheckIn datetime,
#CheckOut datetime
SELECT #EmployeeId = EmployeeId,#CheckIn=CheckIn,#CheckOut=CheckOut from Cygnus_HR_Employee_Attendance_Master where Id=IDENT_CURRENT('Cygnus_HR_Employee_Attendance_Master')
if(#CheckIn==getdate(). && #CheckOut is NULL)
{
Update Cygnus_HR_Employee_Attendance_Master set #CheckOut=#CheckIn.addHours(4) where EmployeeId = #EmployeeId and CheckOut=is NULL
}
END
Use
SET #CheckOut = DATEADD(HH,4,#CheckIn)

Triggers not working when inserting data through OPEN XML in sql ser

I have created a trigger for a asset_verification. Whenever a new record is inserted in this table, the same record is inserted in the asset_verification_history table because of this trigger.
The trigger is as follows
Create trigger [dbo].[tr_insert_after_asset_verification] on [dbo].[asset_verification]
for insert
As
Begin
declare #verification_id int
declare #id int
declare #audit_id int
declare #date date
declare #status varchar(15)
declare #remarks varchar(200)
declare #creationDate datetime
declare #modificationDate datetime
declare #updatedBy int
declare #audit_action varchar(20)
Select #verification_id = i.verification_id from inserted i
If #verification_id IS NOT NULL
Begin
Select #id = i.id from inserted i
Select #audit_id = i.audit_id from inserted i
Select #date = i.date from inserted i
Select #status = i.status from inserted i
Select #remarks = i.remarks from inserted i
Select #creationDate = i.creationDate from inserted i
Select #modificationDate = i.modificationDate from inserted i
Select #updatedBy = i.updatedBy from inserted i
set #audit_action = 'Insert Record'
INSERT INTO [dbo].[asset_verification_history]
([verification_id]
,[id]
,[audit_id]
,[date]
,[status]
,[remarks]
,[creationDate]
,[modificationDate]
,[updatedBy]
,[audit_action])
VALUES
(#verification_id
,#id
,#audit_id
,#date
,#status
,#remarks
,#creationDate
,#modificationDate
,#updatedBy
,#audit_action)
End
End
When I insert the data in the asset_verification table using a procedure in which OPEN XML is used, then this trigger works only for the first record. For the rest of the records the trigger doesn't work
The procedure is as follows
Create procedure [dbo].[usp_AddVerificationBulkData]
(
#vXML XML
)
As
Begin
DECLARE #DocHandle INT
SET NOCOUNT ON
EXEC sp_xml_preparedocument #DocHandle OUTPUT, #vXML
Update asset_verification
set
audit_id = x.AuditId,
id = x.SerialId,
date = x.VerificationDate,
status = x.Status
,remarks = x.Remarks
,creationDate = x.CreatedOn
,modificationDate = x.ModifiedOn
,updatedBy = x.ModifiedBy
From
asset_verification a
Inner Join
OpenXml(#DocHandle,'/ArrayOfAssetVerificationModel/AssetVerificationModel',2)
With(SerialId int, AuditId int, VerificationDate datetime, Status int, Remarks varchar(200), CreatedOn datetime, ModifiedOn datetime, ModifiedBy int) x
On a.audit_id = x.AuditId where a.id = x.SerialId;
INSERT INTO [dbo].[asset_verification]
([id]
,[audit_id]
,[date]
,[status]
,[remarks]
,[creationDate]
,[modificationDate]
,[updatedBy])
select SerialId,AuditId,VerificationDate,Status,Remarks,CreatedOn,ModifiedOn,ModifiedBy from OpenXml(#DocHandle,'/ArrayOfAssetVerificationModel/AssetVerificationModel',2)
With(SerialId int, AuditId int, VerificationDate datetime, Status int, Remarks varchar(200), CreatedOn datetime, ModifiedOn datetime, ModifiedBy int) x
where SerialId NOT IN (select a.id from asset_verification a where a.audit_id = x.AuditId);
End
Problem:- How to make this trigger work for every record that is inserted through Open XML ?
You've made the classic mistake of thinking that triggers fire once-per-row. They dont, it's once-per-action, so the inserted pseudo table holds all the rows affected by the action. Your trigger needs to work in a set based manner, not row based. Try this;
CREATE TRIGGER [dbo].[tr_insert_after_asset_verification] ON [dbo].[asset_verification] FOR INSERT AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[asset_verification_history]
( [verification_id]
,[id]
,[audit_id]
,[date]
,[status]
,[remarks]
,[creationDate]
,[modificationDate]
,[updatedBy]
,[audit_action]
)
SELECT i.verification_id
,i.id
,i.audit_id
,i.date
,i.status
,i.remarks
,i.creationDate
,i.modificationDate
,i.updatedBy
,'Insert Record'
FROM inserted i
WHERE i.verification_id IS NOT NULL
END
As an aside, and strictly speaking, your original trigger will log one row, not necessarily the first.

How to Return Single row?

When i am check the Multi-line function
select * from calculation('2')
Error:
Hi after google i came to know that it's returning two rows(multiple rows) but no idea how to slove this can any give me suggestion please
enter code here
ALTER FUNCTION [dbo].[calculation]
(#AgentID int)
RETURNS #CustomerPosition TABLE
(
-- Add the column definitions for the TABLE variable here
DayAmount Decimal Not NULL,
MonthAmount Decimal NOT NULL,
YearAmount Decimal NOT NULL,
Position INT NOT NULL
)
AS
BEGIN
-- Fill the table variable with the rows for your result set
INSERT Into #CustomerPosition(DayAmount,MonthAmount,YearAmount,Position)
Declare #DayAmount as Decimal
set #DayAmount = (select sum(AmountRecevied) as Totoalamountperday from
tblcollections
where AgentID=#AgentID and datepart(dd,Date_Time)= DATEPART(DD,GETDATE())
group by AgentID)
--SET NOCOUNT ON
DECLARE #MonthStartDate As datetime
DECLARE #MonthEndDate As datetime
DECLARE #today datetime
set #today = convert(varchar(12) , getdate() , 101)
SET #MonthStartDate = #today
Declare #MonthAmount as Decimal
set #MonthAmount = (select sum(AmountRecevied)as Totoalamountperday from
tblcollections
where AgentID=#AgentID and Date_Time between dateadd(dd,- datepart(dd,#MonthStartDate),#MonthStartDate+1) and dateadd(dd,-1,dateadd(mm,+1,#MonthStartDate))
group by AgentID)
Declare #YearAmount as Decimal
set #YearAmount=(select sum(AmountRecevied) as Totoalamountpermonth from
tblcollections
where AgentID=#AgentID and datepart(YYYY,Date_Time) =YEAR(GETDATE())
group by AgentID)
Declare #Position as Decimal
set #Position = (select [RowNumber] = ROW_NUMBER() OVER(ORDER BY AgentID DESC) from
tblcollections
where AgentID=#AgentID
group by AgentID
)
RETURN
END
Try something like this:
SELECT TOP 1 ...

Date range overlapping check constraint

I've a simple table in sql server 2005 with 3 columns: DateStart, DateEnd and Value. I tried to set a "table check constraint" to avoid inserting overlapping records. For instance if in such table there is a record with DateStart = 2012-01-01 (first January) and DateEnd 2012-01-15 (15th January) than Check constraint must avoid inserting a record with DateStart=2012-01-10 (no care DateEnd), a record with DateEnd=2012-01-10 (no care DateStart) or a record with DateStart 2011-12-10 and DateEnd 2012-02-01.
I defined a UDF in such way:
CREATE FUNCTION [dbo].[ufn_checkOverlappingDateRange]
(
#DateStart AS DATETIME
,#DateEnd AS DATETIME
)
RETURNS BIT
AS
BEGIN
DECLARE #retval BIT
/* date range at least one day */
IF (DATEDIFF(day,#DateStart,#DateEnd) < 1)
BEGIN
SET #retval=0
END
ELSE
BEGIN
IF EXISTS
(
SELECT
*
FROM [dbo].[myTable]
WHERE
((DateStart <= #DateStart) AND (DateEnd > #DateStart))
OR
((#DateStart <= DateStart) AND (#DateEnd > DateStart))
)
BEGIN
SET #retval=0
END
ELSE
BEGIN
SET #retval=1
END
END
RETURN #retval
END
Then thought check could be this:
ALTER TABLE [dbo].[myTable] WITH CHECK ADD CONSTRAINT [CK_OverlappingDateRange] CHECK ([dbo].[ufn_checkOverlappingDateRange]([DateStart],[DateEnd])<>(0))
But even with [myTable] empty EXISTS Operator returns true when i insert first record. Where i'm wrog ? Is it possible to set a constraint like this ?
BTW I consider DateStart includes in range and DateEnd excludes from range.
The CHECK is being executed after the row has been inserted, so the range overlaps with itself.
You'll need to amend your WHERE to include something like: #MyTableId <> MyTableId.
BTW, your WHERE expression can be simplified.
Ranges don't overlap if:
end of the one range is before the start of the other
or start of the one range is after the end of the other.
Which could be written in SQL like:
WHERE #DateEnd < DateStart OR DateEnd < #DateStart
Negate that to get the ranges that do overlap...
WHERE NOT (#DateEnd < DateStart OR DateEnd < #DateStart)
...which according to De Morgan's laws is the same as...
WHERE NOT (#DateEnd < DateStart) AND NOT (DateEnd < #DateStart)
...which is the same as:
WHERE #DateEnd >= DateStart AND DateEnd >= #DateStart
So your final WHERE should be:
WHERE
#MyTableId <> MyTableId
AND #DateEnd >= DateStart
AND DateEnd >= #DateStart
[SQL Fiddle]
NOTE: to allow ranges to "touch", use <= in the starting expression, which would produce > in the final expression.
CREATE TABLE [dbo].[TEMPLATE] (
[ID] BIGINT IDENTITY (1, 1) NOT NULL,
[DATE_START] DATETIME NOT NULL,
[DATE_END] DATETIME NOT NULL,
[TEMPLATE_NAME] VARCHAR (50) NOT NULL,
CONSTRAINT [PK_TEMPLATE] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_current_start_and_end_dates_in_sequence] CHECK ([DATE_START]<=[DATE_END])
);
go
CREATE FUNCTION [dbo].[Check_Period]
(
#start DateTime,
#end DateTime
)
RETURNS INT
AS
BEGIN
declare #result INT = 0 ;
set #result = (Select count(*) from [dbo].[TEMPLATE] F where F.DATE_START <= #start and F.DATE_END >= #start );
set #result = #result +
(
Select count(*) from [dbo].[TEMPLATE] F where F.DATE_START <= #end and F.DATE_END >= #end
)
RETURN #result
END
go
ALTER TABLE [dbo].[TEMPLATE]
ADD CONSTRAINT [FK_overlap_period_t]
CHECK ([dbo].[Check_Period]([DATE_START],[DATE_END])=(2));
go
Insert Into [dbo].[TEMPLATE] (DATE_START, DATE_END, TEMPLATE_NAME) values ('2020-01-01','2020-12-31', 'Test1');
-- (1 row(s) affected)
Insert Into [dbo].[TEMPLATE] (DATE_START, DATE_END, TEMPLATE_NAME) values ('2021-01-01','2022-12-31', 'Test2');
-- (1 row(s) affected)
Insert Into [dbo].[TEMPLATE] (DATE_START, DATE_END, TEMPLATE_NAME) values ('2020-01-01','2020-12-31', 'Test3');
-- The INSERT statement conflicted with the CHECK constraint "FK_overlap_period_t".
I'd just like to add onto the Answer of Branko Dimitrijevic the case where the DateEnd is null since I currently have such a scenario.
This can occur when you're keeping punch in logs and the user is still logged in.
WHERE
#MyTableId <> MyTableId
AND #DateEnd >= DateStart
AND DateEnd >= #DateStart
OR #DateEnd >= DateStart
AND DateEnd is null
OR #DateStart >= DateStart
AND DateEnd is null
I don't know how well this query is performance wise, I'm sure there are ways to optimize it.

little problem with mssql cursor, insert in temp table within the cursor

i'm having a little trouble with my stored procedure.
I've written a stored procedure where i'm using a cursor.
Every thing works fine till the place where i'm inserting values from the cursor to the temp table.
here is the error
Msg 156, Level 15, State 1, Procedure ors_DailyReportMessageStatus, Line 36
Incorrect syntax near the keyword 'where'.
and here is the code
ALTER PROCEDURE [dbo].[ors_DailyReportMessageStatus]
-- Add the parameters for the stored procedure here
#startDate datetime, #endDate datetime
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #num int;
DECLARE #stat varchar(20);
DECLARE #statusCursor CURSOR
SET #statusCursor = CURSOR FOR
select count(ms.status) as message, ms.status from message_status ms JOIN [message] m on m.id=ms.message_id
where (m.ADDED_ON >= #startDate AND m.ADDED_ON < #endDate) GROUP BY ms.status
SET NOCOUNT ON;
if object_id('tempdb..#tempdailystatus') is not null
begin
drop table #tempdailystatus
end
CREATE TABLE #tempdailystatus(id int identity, total int , [status] varchar(20));
insert into #tempdailystatus ([status])
select distinct([status]) from message_status;
open #statusCursor
fetch next from #statusCursor into #num, #stat;
while ##FETCH_STATUS = 0
begin
-- this is where the error is
insert into #tempdailystatus (total) values (#num) where id = (select id from #tempdailystatus where [status] = #stat)
-- this were just to see whether the cursor is ok. and it is
--print #stat
--print #num ;
fetch next from #statusCursor into #num,#stat
end
close #statusCursor
deallocate #statusCursor
-- Insert statements for procedure here
-- SELECT * from #tempdailystatus
drop table #tempdailystatus
END
Am I possibly ignoring something? it seems like i'm forgetting something.
Thanks you for reading this and for giving suggestion .will appreciate it ^_^
try replacing:
insert into #tempdailystatus (total) values (#num) where id = (select id from #tempdailystatus where [status] = #stat)
with:
If Exists(Select 1 From #tempdailystatus where [status] = #stat)
Begin
insert into #tempdailystatus (total) Values(#num)
End
I made some assumptions about your logic, so you may need to adjust accordingly.

Resources