Stored procedure. Check if constraint are used in another table - sql

I get this error message:
The DELETE statement conflicted with the REFERENCE constraint
"FK_FieldMapper_Field". The conflict occurred in database "SCAM",
table "dbo.FieldMapper", column 'FieldID'.
I have some accesshelpers which has a given amount of fields. These Fields can be used by multiple accesshelpers..
When I delete an accesshelper, I need to check if the fields in the given accesshelper are used by other accesshelpers. If they are, I shall delete the accesshelper, but not the fields, as that would break other accesshelpers.
How do i do that?
This is what I have come up with on my own so far.
USE [SCAM]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[deleteAccessHelperById]
#Id int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #projectIds TABLE (id int);
INSERT INTO #projectIds ([id]) SELECT dbo.AccessHelperMapper.ProjectID FROM dbo.AccessHelperMapper WHERE dbo.AccessHelperMapper.AccessHelperID = #Id;
DELETE dbo.AccessHelperMapper WHERE dbo.AccessHelperMapper.AccessHelperID = #Id;
DECLARE #fieldIds TABLE (id int);
INSERT INTO #fieldIds ([id]) SELECT dbo.FieldMapper.FieldID FROM dbo.FieldMapper WHERE dbo.FieldMapper.AccessHelperID = #Id;
DECLARE #AHfields TABLE (id int)
Insert into #AHfields
Select fids.id from dbo.FieldMapper, #fieldIds as fids
where dbo.FieldMapper.AccessHelperID != #Id
and fids.id != dbo.FieldMapper.FieldID;
delete dbo.FieldMapper where dbo.FieldMapper.AccessHelperID = #Id and dbo.FieldMapper.FieldID IN (SELECT d.id FROM #AHfields as d);
delete dbo.Field where dbo.Field.ID in (SELECT g.id FROM #AHfields as g);
delete dbo.AccessHelper where dbo.AccessHelper.ID = #Id;
END

Try the below code. Hope this is what you were looking for:
USE [SCAM]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[deleteAccessHelperById]
#Id int
AS
BEGIN
SET NOCOUNT ON;
DELETE dbo.AccessHelperMapper WHERE dbo.AccessHelperMapper.AccessHelperID = #Id;
DECLARE #fieldIds TABLE (id int);
INSERT INTO #fieldIds ([id]) SELECT dbo.FieldMapper.FieldID FROM dbo.FieldMapper WHERE dbo.FieldMapper.AccessHelperID = #Id
AND dbo.FieldMapper.FieldID NOT IN (SELECT DISTINCT dbo.FieldMapper.FieldID FROM dbo.FieldMapper WHERE dbo.FieldMapper.AccessHelperID != #Id);
delete dbo.FieldMapper where dbo.FieldMapper.AccessHelperID = #Id --and dbo.FieldMapper.FieldID IN (SELECT d.id FROM #fieldIds as d);
delete dbo.Field where dbo.Field.ID in (SELECT g.id FROM #fieldIds as g);
delete dbo.AccessHelper where dbo.AccessHelper.ID = #Id;
END

First of all you need a transaction because you have partial data changes which cannot be applied separately.
ALTER PROCEDURE [dbo].[deleteAccessHelperById]
#Id int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #fieldIds TABLE (id int);
BEGIN TRY
BEGIN TRAN
DELETE ahm
FROM dbo.AccessHelperMapper ahm
WHERE ahm.AccessHelperID = #Id;
DELETE fm
OUTPUT DELETED.fieldID
INTO #fieldIds(id)
FROM dbo.FieldMapper fm
WHERE fm.AccessHelperID = #Id
DELETE f
FROM dbo.Field f
WHERE exists(select 1 from #fieldIDs fid where fid.ID = f.ID)
and not exists(
select 1 from dbo.FieldMapper fm
where fm.fieldID = f.ID
and fm.AccessHelperID != #ID -- redundant, may be removed.
)
DELETE ah
FROM dbo.AccessHelper ah
where ah.ID = #Id
COMMIT TRAN
END TRY
BEGIN CATCH
IF XACT_STATE() IN (1, -1)
ROLLBACK TRAN
THROW;
END CATCH
END

Related

Updating two tables at once with values off the first one, and a variable

I'm trying to update with a passed variable in only the first row that has value NULL (multiple rows could have NULL in this column, but I need just the one),
Then I need to get the row affected (the primary key) and update the other table with it.
Here's what my two tables look like:
table1
id | some_value | ref_table2_id_fk
table2
id | name | ref_table1_id_fk
In my stored procedure I'm grabbing the passed value as #passed as int, then I try the following:
BEGIN
SET NOCOUNT ON;
DECLARE #id AS INT;
DECLARE #temp TABLE (id int);
BEGIN TRANSACTION;
BEGIN TRY
UPDATE TOP (1) [dbo].table1
SET ref_table2_id_fk = #passed
OUTPUT inserted.id INTO #temp
WHERE ref_table2_id_fk = NULL
UPDATE [dbo].table2
SET ref_table1_id_fk = #temp.id
FROM table2
JOIN #temp i on i.id = table2.id;
SET #id = ##IDENTITY
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
--some error
ROLLBACK TRANSACTION;
RETURN 0;
END
END CATCH;
IF ##TRANCOUNT > 0
BEGIN
--success
COMMIT TRANSACTION;
RETURN #Id;
END
END
As pointed out by Dale ##identity doesn't work in update. My intention is to simply know if the transaction went through or not.
I think the following code does what you are asking. Things fixed:
where ref_table2_id_fk = null should be where ref_table2_id_fk is null
You can't use TOP in an update statement you need a sub-query to get the id.
You're not providing an id in #temp to join onto table2 - you need the table1 id and the table2 id for a joined update.
If I understand your logic the id you want to return is #passed - you already have it.
#temp.id should be i.id since you've (rightly) aliased it
declare #Passed int = 3;
declare #table1 table (id int, some_value varchar(12), ref_table2_id_fk int);
declare #table2 table (id int, some_value varchar(12), ref_table1_id_fk int);
insert into #table1 (id)
select 1 union all select 2;
insert into #table2 (id)
select 3 union all select 4;
select * from #table1;
select * from #table2;
DECLARE #id AS INT, #Result bit = 0;
DECLARE #temp TABLE (id int, fk int);
BEGIN TRANSACTION;
BEGIN TRY
UPDATE #table1
SET ref_table2_id_fk = #passed
OUTPUT #passed, inserted.id INTO #temp
WHERE id = (
select top 1 id
from #table1
where ref_table2_id_fk is NULL
-- Optionally order by if you have a priority here
);
UPDATE T2
SET ref_table1_id_fk = i.fk
FROM #table2 T2
JOIN #temp i on i.id = T2.id
where T2.id = #passed;
-- If we get here then everything worked
-- Return #Result at the end of the proc
SET #Result = 1;
END TRY
begin catch
no_op:;
end catch
select * from #table1;
select * from #table2;

SQL Server trigger interfering with select statement

We have a trigger on a table that fires during an update. We use Entity Framework on the front end. So, we don't want to see the intermediate results (the results from the trigger) when we do an update. I have SET NOCOUNT ON in my trigger:
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
But when we execute the update, we see the intermediate results first and that throws Entity Framework.
Here is the sql command:
Declare #UserID int
set #UserID = 26
declare #WorkItemID int
Declare #UserRoles TABLE ( roleid INT ,parentroleid INT ,rolename VARCHAR(100) )
INSERT INTO #UserRoles SELECT R.id,R.parentroleid,R.ROLE FROM userroles UR
INNER JOIN roles R ON UR.roleid = R.id WHERE UR.userid = #UserID
Update MedCMDWorkItems with (UPDLOCK) Set AssignedTo = #UserID, AssignedAt =
getdate(), LastUpdatedAt = getdate(), LastUpdatedBy = #UserID, #WorkItemID =
ID
where ID = (SELECT TOP 1 PR.ID FROM MedCMDWorkItems PR --with (UPDLOCK)
JOIN TaskTypes TT on PR.TaskTypeID = TT.ID WHERE PR.AssignedTo IS NULL AND
PR.ClosedAt IS NULL
AND Exists (SELECT 1 FROM #UserRoles WHERE roleid in (852,772)) AND
((PR.WorkItemTypeID = (select 29 FROM #UserRoles UR WHERE UR.RoleID = 852)
AND TT.Task in (SELECT Substring(UR1.rolename, Charindex('|', UR1.rolename)
+ 1, Len(UR1.rolename)) FROM #UserRoles UR1 WHERE UR1.parentroleid in (852))
) OR
(PR.WorkItemTypeID = (select 28 FROM #UserRoles UR WHERE UR.RoleID = 772)
AND TT.Task in (SELECT Substring(UR1.rolename, Charindex('|', UR1.rolename)
+ 1, Len(UR1.rolename)) FROM #UserRoles UR1 WHERE UR1.parentroleid in (772))
) )
ORDER BY (CASE WHEN PR.IsUrgent IS NULL THEN 'False' ELSE PR.IsUrgent END)
DESC, ( CASE WHEN PR.Priority IS NULL THEN 9999 ELSE PR.Priority END ),
PR.CreatedAt)
Select * From MedCMDWorkItems PRI Where ID = #WorkItemID
Here is the trigger:
USE [CombinedWorkflow]
GO
/****** Object: Trigger [dbo].[trgForMedCMDWorkItemsUpdate] Script Date:
4/3/2018 5:20:22 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
declare #NewAssignedAt DateTime;
declare #OldAssignedAt DateTime;
declare #NewPulledAt DateTime;
declare #ID int;
select #NewPulledAt = i.PulledAt from inserted i;
select #NewAssignedAt = i.AssignedAt from inserted i;
select #OldAssignedAt = d.AssignedAt from deleted d;
select #ID = i.ID from inserted i;
--If work item is being un-assigned
if(#OldAssignedAt is NOT NULL and #NewAssignedAt is NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set PulledAt = NULL
Where ID = #ID
END
--if work item is being assinged
ELSE if(#NewAssignedAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set FirstAssignedAt = #NewAssignedAt
Where FirstAssignedAt is NULL and ID = #ID
END
-- if work item is being pulled
ELSE if(#NewPulledAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set AssignedAt = #NewPulledAt
Where AssignedAt is NULL and ID = #ID
SET NOCOUNT ON;
Update MedCMDWorkItems
Set FirstAssignedAt = #NewPulledAt
Where FirstAssignedAt is NULL and ID = #ID
END
Select 1
END
When I execute in SSMS with the trigger on under results, I get 2 results, first indicating 1 row was updated, then a second indicating the results of the query.
If I disable the trigger and execute the command I don't get the first result, and that is my desired behavior with the trigger enabled.
How do I set this trigger up so it fires but does not cause the additional results?
Thanks,
Sammer
you need to use inserted and deleted as tables, they can hold more than one row so you never can fill a variable from it.
In stead you have to build it like this example :
your code (first part of it)
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE AS
BEGIN
SET NOCOUNT ON;
declare #NewAssignedAt DateTime;
declare #OldAssignedAt DateTime;
declare #NewPulledAt DateTime;
declare #ID int;
select #NewPulledAt = i.PulledAt from inserted i;
select #NewAssignedAt = i.AssignedAt from inserted i;
select #OldAssignedAt = d.AssignedAt from deleted d;
select #ID = i.ID from inserted i;
--If work item is being un-assigned
if(#OldAssignedAt is NOT NULL and #NewAssignedAt is NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set PulledAt = NULL
Where ID = #ID
END
can be replaced by something like this (not tested because i dont have your database)
ALTER TRIGGER [dbo].[trgForMedCMDWorkItemsUpdate]
ON [dbo].[MedCMDWorkItems]
AFTER UPDATE AS
BEGIN
SET NOCOUNT ON;
update MedCMDWorkItems
Set PulledAt = NULL
where ID in ( select i.Id
from Inserted i
left join deleted d on i.Id = d.Id
where d.AssignedAt is not null
and i.AssignedAt is null
)
Once you get this it will not be so hard to figure out how to adjust the rest of your trigger
Thanks everyone for the input. I figured out the issue. The trigger had this at the end:
...
END
-- if work item is being pulled
ELSE if(#NewPulledAt IS NOT NULL)
BEGIN
SET NOCOUNT ON;
Update MedCMDWorkItems
Set AssignedAt = #NewPulledAt
Where AssignedAt is NULL and ID = #ID
SET NOCOUNT ON;
Update MedCMDWorkItems
Set FirstAssignedAt = #NewPulledAt
Where FirstAssignedAt is NULL and ID = #ID
END
Select 1
END
No idea what that last line (Select 1) is supposed to be for (I didn't write the trigger :)), but it was causing the unwanted results being displayed.
Sammer

Separate rows in multiple delete SQL server

I have a trigger. When i delete something in my table i want to create a row in other table named Archive with the row i deleted. All works fine. The problem is when i delete more than one row in the same time. It store just the first row in the table Archive. I think about creating a loop but how i separate them in subqueries to select every row. How do i solve that?
ALTER TRIGGER [dbo].[trigArhiva]
ON [dbo].[student]
AFTER DELETE
AS
SET NOCOUNT ON;
BEGIN
declare #nume varchar(45);
declare #anStudiu int;
declare #prenume varchar(45);
declare #CNP char(13);
declare #grupa int;
declare #idFacult int;
declare #rowss int;
declare #i int;
select #rowss = count(*) from DELETED;
set #i = 1;
while (#i <= #rowss)
begin
select #nume = nume from DELETED;
select #anStudiu = anStudiu from DELETED;
select #prenume = prenume from DELETED;
select #CNP = CNP from DELETED;
select #grupa = idGrupa from DELETED;
select #idFacult = idFacult from DELETED;
insert into arhivaStud(nume, prenume, CNP, grupa, idFacult, anStudiu) values (#nume, #prenume, #CNP, #grupa, #idFacult, #anStudiu);
set #i = #i+1;
end
END
I think you should use something like this (deleted is an "internal" representation of all records deleted during the operation which recall the trigger):
ALTER TRIGGER [dbo].[trigArhiva]
ON [dbo].[student]
AFTER DELETE
AS
SET NOCOUNT ON;
BEGIN
INSERT INTO arhivaStud(nume, prenume, CNP, grupa, idFacult, anStudiu)
SELECT nume, prenume, CNP, idgrupa, idFacult, anStudiu FROM deleted
END

Modify SQL Trigger to work with BULK INSERT

I have a SQL Trigger that doesn't fire because the records in the table are inserted through a BULK INSERT. I do not have access to the code that inserts the records so I need to modify this trigger to handle the BULK INSERT. This is the trigger:
USE [testdata]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[Trigger_test] ON [dbo].[test]
AFTER INSERT , UPDATE
AS
BEGIN
DECLARE #BatchId int, #Ethanol decimal(18,6), #Glucose decimal(18,6), #SampleAge varchar(50);
SELECT #BatchId = CONVERT(int,bd.[BatchId]),
#Ethanol = CONVERT(decimal(18,2),[Ethanol]),
#Glucose= CONVERT(decimal(18,2),[Glucose]),
#SampleAge = bd.SampleCode
from INSERTED bd
update [dbo].[DeSchedule]
SET
[Ethanol] = #Ethanol,
[Glucose] = #Glucose,
[SampleCompleted] = 1
WHERE [BatchID] = #BatchId AND [SampleAge] = #SampleAge
END
Can anyone help me in modifying this trigger to handle the BULK INSERT.
Unless you can modify the BULK INSERT statement you are stuck. By default triggers do NOT run during a bulk insert. You must explicitly turn them on in the command with the FIRE_TRIGGER option.
Only need to edit BULK INSERT File below:
BULK INSERT AdventureWorks2012.Sales.SalesOrderDetail
FROM 'f:\orders\lineitem.tbl'
WITH
(
FIELDTERMINATOR =' |'
, ROWTERMINATOR = ':\n'
, FIRE_TRIGGERS
);
USE [testdata]
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
ALTER TRIGGER [dbo].[Trigger_test] ON [dbo].[test]
AFTER INSERT , UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #BatchId int, #Ethanol decimal(18,6), #Glucose decimal(18,6), #SampleAge varchar(50);
Declare CurI cursor for
SELECT [BatchId],[Ethanol] ,[Glucose], SampleCode from INSERTED
Open CurI
fetch next from CurI into #BatchId,#Ethanol,#Glucose, #SampleAge
while ##fetch_status=0
Begin
update [dbo].[DeSchedule]
SET
[Ethanol] = #Ethanol,
[Glucose] = #Glucose,
[SampleCompleted] = 1
WHERE [BatchID] = #BatchId AND [SampleAge] = #SampleAge
Fetch next from CurI into #BatchId,#Ethanol,#Glucose, #SampleAge
End
Close CurI
Deallocate CurI
END
the problem is it selected only the last row of the inserted table ,i think if you change the query like this it would work
update [dbo].[DeSchedule]
SET
[Ethanol] =(select CONVERT(int,bd.[Ethanol]) from inserted bd),
[Glucose] = (select CONVERT(decimal(18,2),[Glucose]) from inserted bd),
[SampleCompleted] = 1
WHERE [BatchID] = (select CONVERT(int,bd.[BatchId]) from inserted bd) AND [SampleAge] = (select bd.SampleCode from inserted bd)

Storedprocedure to check the value already in the table

I have a stored procedure to insert values in to a table.Here I need to check the values for insert is already in the table .how can I check this in my stored procedure.here is my stored procedure.
USE [Databasse_sync]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[kt_getyoutubevideo]
#ProductID nvarchar(200),
#YoutubeUrl nvarchar(200),
#YoutubeImage nvarchar(200)
AS
INSERT INTO YoutubeVideo(ProductID,YoutubeUrl,YoutubeImage,DATASET)VALUES(#ProductID,#YoutubeUrl,#YoutubeImage,'DAT')
RETURN
Here I need to check the ProducId is same or not?If ProductId is same then Update otherwise Insert.>>>??
USE [Databasse_sync]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[kt_getyoutubevideo]
#ProductID nvarchar(200),
#YoutubeUrl nvarchar(200),
#YoutubeImage nvarchar(200)
AS
MERGE [YoutubeVideo] AS Y
USING (SELECT #ProductID,#YoutubeUrl, #YoutubeImage) AS SourceRow (ProductID,YoutubeUrl,YoutubeImage)
ON Y.[ProductID] = SourceRow.ProductID
WHEN MATCHED THEN
UPDATE SET
Y.[ProductID] = SourceRow.ProductID
,Y.[YoutubeUrl] = SourceRow.YoutubeUrl
,Y.[YoutubeImage] = SourceRow.YoutubeImage
WHEN NOT MATCHED THEN
INSERT (ProductID,YoutubeUrl, YoutubeImage, DATASET)
VALUES (SourceRow.ProductID,SourceRow.YoutubeUrl, SourceRow.YoutubeImage,'DAT');
RETURN
IF NOT EXISTS(SELECT TOP 1 ProductID FROM YoutubeVideo WHERE ProductID=#ProductID) BEGIN
--INSERT HERE
END
ELSE BEGIN
--UPDATE HERE
END
DECLARE #EXISTS BIT
SELECT #EXISTS = 1
WHERE EXISTS
(SELECT ID FROM YouTubeVideo
WHERE ID = #ProductID
AND YoutubeUrl = #YoutubeUrl)
IF #Exists = 1
BEGIN
-- Update
END
ELSE
BEGIN
-- INSERT
END
#Arun
ALTER PROCEDURE [dbo].[kt_getyoutubevideo]
#ProductID nvarchar(200),
#YoutubeUrl nvarchar(200),
#YoutubeImage nvarchar(200)
AS
BEGIN
DECLARE #counter INT
#counter=SELECT COUNT(ProductID) FROM YoutubeVideo WHERE ProductID=#ProductID
IF(#counter = 0)
BEGIN
INSERT INTO YoutubeVideo(ProductID,YoutubeUrl,YoutubeImage,DATASET)VALUES(#ProductID,#YoutubeUrl,#YoutubeImage,'DAT')
END
END
if u want to check record is inserted or not then u can take one out parameter and chek recored is inserted or not.
try this:
ALTER PROCEDURE [dbo].[kt_getyoutubevideo]
#ProductID nvarchar(200),
#YoutubeUrl nvarchar(200),
#YoutubeImage nvarchar(200)
AS BEGIN
DECLARE #counter int
#counter = SELECT count(ProductID) FROM YoutubeVideo WHERE ProductID=#ProductID
IF(#counter = 0)
BEGIN
INSERT INTO YoutubeVideo(ProductID,YoutubeUrl,YoutubeImage,DATASET)VALUES(#ProductID,#YoutubeUrl,#YoutubeImage,'DAT')
End
ELSE
begin
UPDATE SET
ProductID = #ProductID
,YoutubeUrl = #YoutubeUrl
,YoutubeImage = #YoutubeImage
END END