I have a table EmployeeLeaves having columns:
EmployeeID int,
LeaveTypeID int,
LeaveDate datetime2,
IsHalfDay bit,
DateCreated datetime2,
Createdby varchar
I request you to help me out with a trigger that prevent same date for the column "LeaveDate". There are no date ranges like "Todate" and "FromDate" in the table.
Thanks in advance.
Please refer the sample demo,
CREATE TABLE EmployeeLeaves
(
EmployeeID INT,
LeaveTypeID INT,
LeaveDate DATETIME2,
IsHalfDay BIT,
DateCreated DATETIME2,
Createdby VARCHAR(50)
)
insert into EmployeeLeaves
values (1,1,'2016-01-01',0,getdate(),'Admin'),
(2,1,'2016-01-01',0,getdate(),'Admin'),
(3,1,'2016-01-01',0,getdate(),'Admin'),
(4,1,'2016-01-01',0,getdate(),'Admin'),
(5,1,'2016-01-01',0,getdate(),'Admin')
SELECT *
FROM EmployeeLeaves
METHOD-1 using Unique Constraint
--Introduce the unique constraint
ALTER TABLE EmployeeLeaves
ADD CONSTRAINT uq_employeeid_leavedate UNIQUE (EmployeeId, LeaveDate)
--Try to create overlap
insert into EmployeeLeaves
values (1,1,'2016-01-01',0,getdate(),'Admin')
--You will get the following error
--Msg 2627, Level 14, State 1, Line 1
--Violation of UNIQUE KEY constraint 'uq_employeeid_leavedate'. Cannot insert duplicate key in object 'dbo.EmployeeLeaves'. The duplicate key value is (1, 2016-01-01 00:00:00.0000000).
--The statement has been terminated.
--Insert proper date
insert into EmployeeLeaves
values (1,1,'2016-01-02',0,getdate(),'Admin')
--Check the result
SELECT *
FROM EmployeeLeaves
METHOD-2 using Instead of trigger
ALTER TRIGGER Trigger_Test
ON EmployeeLeaves
INSTEAD OF INSERT
AS
BEGIN
IF EXISTS(SELECT TOP 1 1
FROM inserted I
INNER JOIN EmployeeLeaves e
ON i.EmployeeID = e.EmployeeID
AND i.LeaveDate = e.LeaveDate)
BEGIN
RAISERROR (N'Overlapping range.',16,1);
ROLLBACK TRANSACTION;
END
ELSE
BEGIN
INSERT INTO EmployeeLeaves
SELECT *
FROM inserted
END;
END
You can add a unique constraint on leave date field or you can check and raise exception using
if exist( select top 1 EmployeeID from table where EmployeeID = #emploeeid and
datediff(day,leavedate,#applydate)=0 and leavetypeid= #leavetypeid)
begin raise_error('leave already exist',1,1) end
Related
I would like to SUM a value called amount from table 1, the considered, to be summed, values should only be the ones presenting in table 2. Meaning that, for the amount of row 1 in table 1 to be considered in the sum. the ID of that row 1 should be present in table 2.
Thanks,
This might be the answer but you really should have put some example tables in your example. I fancied helping as have 10 mins, this example you can run.
You can see that table 1 is referenced twice from Table2 and 3 just the once ,so the result ignores multiple occurrences, hence the WHERE EXISTS syntax.
This sums up all the numbers in Table1 that are referenced in Table2.
BEGIN TRANSACTION
BEGIN TRY
CREATE TABLE #Table1 (
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[Number] DECIMAL NOT NULL
)
INSERT INTO #Table1 ([Number])
VALUES('1'),
('1'),
('1'),
('1')
SELECT * FROM #Table1
CREATE TABLE #Table2 (
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
Table1Id INT NOT NULL,
CONSTRAINT FK_Table2_Table1 FOREIGN KEY (Table1Id) REFERENCES #Table1 (Id)
)
INSERT INTO #Table2 ([Table1Id])
VALUES('1'),
('1'),
('3')
SELECT * FROM #Table2
SELECT SUM(T1.Number) AS SummedNumbersThatAreReferencedByTable2
FROM #Table1 AS T1
WHERE EXISTS(
SELECT TOP 1 *
FROM #Table2 AS T2
WHERE T2.Table1Id = T1.Id
)
ROLLBACK TRANSACTION
END TRY
BEGIN CATCH
PRINT 'Rolling back changes, there was an error!!'
ROLLBACK TRANSACTION
DECLARE #Msg NVARCHAR(MAX)
SELECT #Msg=ERROR_MESSAGE()
RAISERROR('Error Occured: %s', 20, 101,#msg) WITH LOG
END CATCH
If this is the answer then please mark it as so, cheers
I am trying to create a constraint to validate if a relation exists
I have tried to create a procedure and then use it in check constraint. Apparently that does not seem to work.
These are my tables:
STOCKITEMS table:
StockItemId INT
StockItemName VARCHAR
ColorId INT
COLOR table:
ColorId INT
ColorName VARCHAR
This is my stored procedure:
CREATE PROCEDURE USP_ValidateColor
(#Color NVARCHAR(50))
AS
IF NOT EXISTS(SELECT ColorName FROM WareHouse.Colors WHERE ColorName = #Color)
BEGIN
DECLARE #Id INT
SET #Id = (SELECT TOP(1) ColorId + 1 FROM Warehouse.Colors
ORDER BY ColorId DESC)
INSERT INTO Warehouse.Colors
VALUES (#Id, #Color)
PRINT 'Does not exist';
END;
ELSE
PRINT 'Exists';
So if a user insert into the table stock items, I want a check that checks if the colorId already exists in the color table
If it does not, then insert that colorname into colors and. I was thinking about using a constraint check with my procedure, but can't fix the query.
Don't use an SP to check a constraint, use a foreign key:
CREATE TABLE Colour (ColourID int PRIMARY KEY, --This should really have a name
ColourName varchar(20));
CREATE TABLE StockItem (StockItemID int PRIMARY KEY, --This should really have a name too
StockItemName varchar(20),
ColourID int);
ALTER TABLE dbo.StockItem ADD CONSTRAINT Colour_FK FOREIGN KEY (ColourID) REFERENCES dbo.Colour(ColourID);
Then, if you try to insert something into the StockItem table, it'll fail unless the colour exists:
INSERT INTO dbo.Colour (ColourID,
ColourName)
VALUES (1,'Green'),(2,'Blue');
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES(1,'Paint',1); --works
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES (1,'Wood Panels',3); --fails
GO
--clean up
DROP TABLE dbo.StockItem;
DROP TABLE dbo.Colour;
For checking, use a UNIQUE check constraint. If you want to insert a color only if it doesn't exist, use INSERT .. FROM .. WHERE to check for existence and insert in the same query.
The only "trick" is that FROM needs a table. This can be fixed using a table value constructor to create tables out of the values to insert. If the stored procedure accepts a table-valued parameter, there's no problem.
This example uses a LEFT JOIN to insert non-matching values :
declare #colors table (Color nvarchar(10) UNIQUE)
insert into #colors VALUES ('green')
select * from #colors;
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (1 row affected)
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (0 rows affected)
select * from #colors;
-- green
-- red
The same using a subquery:
insert into #Colors (Color)
select new.Color
from
(VALUES ('red'),
('green')) new(Color)
where not exists (select 1
from #colors
where color=new.Color);
By using the UNIQUE constraint we ensure that duplicate entries can't be inserted
There are two table exist in DB, Audit and AuditField, following is the Create table code:
-- Primary key: ID
CREATE TABLE [dbo].[Audit](
[ID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[TypeName] [varchar](50) NOT NULL
)
GO
-- Primary key: ID
CREATE TABLE [dbo].[AuditField](
[ID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[AuditID] [int] NOT NULL,
[Field1] [varchar](50) NOT NULL
)
GO
-- Set foreign key on AuditField table
ALTER TABLE [dbo].[AuditField]
ADD CONSTRAINT [FK_AuditFiled_Audit] FOREIGN KEY([AuditID])
REFERENCES [dbo].[Audit] ([ID])
GO
Then I prepared some test data:
DECLARE #audit TABLE
(
ID int not null,
TypeName varchar(50)
)
DECLARE #auditField TABLE
(
AuditID int not null,
Field1 varchar(50)
)
-- ADD TEST DATA
DECLARE #i int = 1
DECLARE #rowCount int = 500
WHILE #i<=#rowCount
BEGIN
INSERT INTO #audit
VALUES(#i, 'SomeTypeName')
INSERT INTO #auditField
(AuditID,Field1)
VALUES(#i,'SomeThing')
SET #i += 1
END
Finally, i run following transaction to insert test data to these two table:
begin transaction
INSERT INTO dbo.Audit
SELECT TypeName
FROM #audit
ORDER BY ID
declare #lastIdentity int = ##identity
declare #offSet int = #lastIdentity - #rowCount
INSERT INTO dbo.AuditField
SELECT AuditID+#offSet AS AuditID, Field1
FROM #auditField
ORDER BY AuditID
commit transaction
When this transaction run concurrent, dead lock occur, one process are failed, the other got an error:
Msg 547, Level 16, State 0, Line 40 The INSERT statement conflicted
with the FOREIGN KEY constraint "FK_AuditFiled_Audit". The conflict
occurred in database "MyDB", table "dbo.Audit", column 'ID'.
There is no trigger on Audit and AuditField table.
Sorry for the format of the code, I really need an answer why this dead lock occur, thanks.
One thing should be clear, the data of AuditField table comes from #auditField, As #Bogdan answer I rewrite like this:
begin transaction
INSERT INTO dbo.Audit
OUTPUT inserted.ID INTO #temp
SELECT TypeName
FROM #audit
INSERT INTO #idMapping
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS RowNumber, ID
FROM #temp
INSERT INTO dbo.AuditField
SELECT m.ID AS AuditID, Field1
FROM #auditField af
INNER JOIN #idMapping m ON af.AuditID = m.RowNumber
commit transaction
This is and Read - Write deadlock:
As you can see, every transaction has successfully acquired an [e]X[clusive] lock and it requests a S[hared] lock. The question is why a transaction try to read rows X locked by another transaction.
And the answer is bellow:
1) Following piece of source code
declare #lastIdentity int = ##identity
declare #offSet int = #lastIdentity - #rowCount
assumes that IDENTITY values generated by every
INSERT INTO dbo.Audit
SELECT TypeName
FROM ...
statement are continues. This is completely wrong as you can see in the following picture:
This means that at some point in time, a transaction could successfully get X locks on inserted rows and then
1) Because inserted rows into Audit aren't continuous and
2) Because of
declare #lastIdentity int = ##identity
declare #offSet int = #lastIdentity - #rowCount
INSERT INTO dbo.AuditField
SELECT AuditID+#offSet AS AuditID, Field1 ...
this last INSERT tries to insert into dbo.AuditField, AuditID values that belong to another transaction and this requires FK validation and, also, means that SQL Server needs to read rows from dbo.Audit. For this S[hared] locks are needed.
To be clear: the root cause of this deadlock is not the FK constraint. The real problem is that source code.
Solution: I would rewrite thus:
begin transaction
INSERT INTO dbo.Audit
OUTPUT inserted.ID, inserted.TypeName INTO #audit (ID, TypeName)
SELECT TypeName
FROM #audit
-- ORDER BY ID -- Isn't necessary
... do something (ex. DELETE) with rows from #audit
INSERT INTO dbo.AuditField (AuditID, ...)
SELECT x.ID, ...
FROM #audit x
-- ORDER BY AuditID
/* or
INSERT INTO dbo.AuditField (AuditID, Field1, ....)
SELECT y.ID, y.ColumnName, ...
FROM (
SELECT x.ID, ...
FROM #audit x
UNPIVOT( ColumnValue FOR ColumnName IN ([TypeName], ...) )
) y
WHERE y.....
*/
commit transaction -- Isn't necessary
You are trying to insert an invalid foreign key value into dbo.AuditField:
SELECT AuditID+#offSet AS AuditID, Field1
Why the #offset? You won't necessarily have an AuditId with that value in the dbo.Audit table.
I have an ordering system where when a new order is placed it is inserted into my table Orders. From there I want to insert the new id into another table Importance which also needs an id from a third table called ImportanceRating.
Table structures:
Order
OrderId uniqueidentifier
TimeOrderPlaced datetime
ProductId uniqueidentifier
EstimatedDeliveryTime datetime
Importance
FK_OrderId uniqueidentifier
FK_ImpRatingId uniqueidentifier
ImportanceRating
ImpRatingId uniqueidentifier
RatingTitle varchar(50)
All of this I want merged in 1 stored procedure. How would I go about with this?
Links to good guides on the subject is more than welcome.
I'm a SPROC newbie
Could you try this?:
CREATE PROCEDURE AddOrderAndRatingSample
-- These are the values you want to insert
#paramTimeOrderPlaced DATETIME
, #paramProductId INT
, #paramEstimatedDeliveryTime DATETIME
, #paramRatingTitle VARCHAR(50)
AS
BEGIN
DECLARE #siOrderId INT
DECLARE #siImpRatingId INT
-- Assuming that `OrderId` in table `Order` is an `identity column`:
INSERT INTO Order (TimeOrderPlaced, ProductId, EstimatedDeliveryTime)
VALUES(#paramTimeOrderPlaced, #paramProductId, #paramEstimatedDeliveryTime)
SET #siOrderId = SCOPE_IDENTITY()
-- Assuming `ImpRatingId` in table `ImportanceRating` is an `identity column`:
INSERT INTO ImportanceRating (RatingTitle)
VALUES(#paramRatingTitle)
SET #siImpRatingId = SCOPE_IDENTITY()
-- And that both `FK_OrderId` and `FK_ImpRatingId`
-- in table `Importance` are not `identity columns`:
INSERT INTO Importance (FK_OrderId, FK_ImpRatingId)
SELECT #siOrderId, #siImpRatingId
END
Could you please try this way:
DECLARE #OrderId INT
INSERT INTO Order (TimeOrderPlaced, ProductId, EstimatedDeliveryTime)
VALUES(#paramTimeOrderPlaced, #paramProductId, #paramEstimatedDeliveryTime)
SET #OrderId = ##IDENTITY -- Last orderId
INSERT INTO ImportanceRating (RatingTitle)
VALUES(#paramRatingTitle)
INSERT INTO Importance (FK_OrderId, FK_ImpRatingId)
SELECT #OrderId, ##IDENTITY -- Here ##IDENTITY returns last ID of ImportanceRating
-- Each inserting time the global variable ##IDENTITY is set with last IDENTITY value
I have a requirement to insert multiple rows into table1 and at the same time insert a row into table2 with a pkID from table1 and a value that comes from a SP parameter.
I created a stored procedure that performs a batch insert with a table valued parameter which contains the rows to be inserted into table1. But I have a problem with inserting the row into table2 with the corresponding Id (identity) from table1, along with parameter value that I have passed.
Is there anyone who implemented this, or what is the good solution for this?
CREATE PROCEDURE [dbo].[oSP_TV_Insert]
#uID int
,#IsActive int
,#Type int -- i need to insert this in table 2
,#dTableGroup table1 READONLY -- this one is a table valued
AS
DECLARE #SQL varchar(2000)
DECLARE #table1Id int
BEGIN
INSERT INTO dbo.table1
(uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active)
SELECT
#uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active
,#G_Active
FROM #dTableGroup
--the above query will perform batch insert using the records from dTableGroup which is table valued
SET #table1ID = SCOPE_IDENTITY()
-- this below will perform inserting records to table2 with every Id inserted in table1.
Insert into table2(#table1ID , #type)
You need to temporarily store the inserted identity values and then create a second INSERT statement - using the OUTPUT clause.
Something like:
-- declare table variable to hold the ID's that are being inserted
DECLARE #InsertedIDs TABLE (ID INT)
-- insert values into table1 - output the inserted ID's into #InsertedIDs
INSERT INTO dbo.table1(ID, Name, Contact, Address, City, State, Zip, Phone, Active)
OUTPUT INSERTED.ID INTO #InsertedIDs
SELECT
#ID, Name, Contact, Address, City, State, Zip, Phone, Active, #G_Active
FROM #dTableGroup
and then you can have your second INSERT statement:
INSERT INTO dbo.table2(Table1ID, Type)
SELECT ID, #type FROM #InsertedIDs
See the MSDN docs on the OUTPUT clause for more details on what you can do with the OUTPUT clause - one of the most underused and most "unknown" features of SQL Server these days!
Another approach using OUTPUT clause and only one statement for inserting data in both destination tables:
--Parameters
DECLARE #TableGroup TABLE
(
Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #Type INT;
--End Of parameters
--Destination tables
DECLARE #FirstDestinationTable TABLE
(
FirstDestinationTableID INT IDENTITY(1,1) PRIMARY KEY
,Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #SecondDestinationTable TABLE
(
SecondDestinationTable INT IDENTITY(2,2) PRIMARY KEY
,FirstDestinationTableID INT NOT NULL
,[Type] INT NOT NULL
,CHECK([Type] > 0)
);
--End of destination tables
--Test1
--initialization
INSERT #TableGroup
VALUES ('Bogdan SAHLEAN', '0721200300')
,('Ion Ionescu', '0211002003')
,('Vasile Vasilescu', '0745600800');
SET #Type = 9;
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
SELECT *
FROM #FirstDestinationTable;
SELECT *
FROM #SecondDestinationTable;
--End of test1
--Test2
--initialization
DELETE #TableGroup;
DELETE #FirstDestinationTable;
DELETE #SecondDestinationTable;
INSERT #TableGroup
VALUES ('Ion Ionescu', '0210000000')
,('Vasile Vasilescu', '0745000000');
SET #Type = 0; --Wrong value
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
DECLARE #rc1 INT, #rc2 INT;
SELECT *
FROM #FirstDestinationTable;
SET #rc1 = ##ROWCOUNT;
SELECT *
FROM #SecondDestinationTable;
SET #rc2 = ##ROWCOUNT;
RAISERROR('[Test2 results] #FirstDestinationTable: %d rows; ##SecondDestinationTable: %d rows;',1,1,#rc1,#rc2);
--End of test1
Since you need all inserted identity values, look at the output clause of the insert statement: http://msdn.microsoft.com/en-us/library/ms177564.aspx