Say I have a self relation table as following :
ID - Name - ParentID
Now everytime that users insert sth in this table I would like to check if the Name inserted is already in the
rows where ParentID equals to the inserted one , if true then rollback the transaction.
But the problem is when I check the rows with the parentID from the inserted table the inserted row is already in the main table too. So, the trigger always rolls back the transaction.
Here is my trigger :
ALTER TRIGGER TG_Check_Existance_In_myTbl
ON myTbl FOR INSERT,UPDATE AS
DEClARE #result BIT
DECLARE #numberOfRows INT
DECLARE #counter INT
DECLARE #names nVARCHAR (30)
DECLARE #name NVARCHAR (30)
SET #result = 0
SET #numberOfRows = (SELECT COUNT (Name)
FROM myTbl
WHERE ParentID IN
(
SELECT ParentID
FROM inserted
)
)
SET #counter = 1;
SELECT #name = Name
FROM inserted
WHILE (#counter <= #numberOfRows)
BEGIN
WITH Q
AS
(
SELECT ROW_NUMBER()
OVER (ORDER BY Name) 'Row', Name
FROM myTbl WHERE ParentID IN
(
SELECT ParentID
FROM inserted
)
)
SELECT #names = Name
FROM Q
WHERE Row = #counter
IF #name = #names
SET #result=1;
SET #counter = #counter + 1
END
IF #result = 1
ROLLBACK TRAN
Unless I am missing something you are making this way too hard.
Why don't you use a unique constraint on the two columns?
table_constraint (Transact-SQL)
Related
I need to update a table in batches, but it does not work. I tried 2 options below.
Both of the options update the first 10 rows but the update is still running. But only 10 rows remain updated.
Seems like update never finishes and count shows more than number of records in the tables to be updated.
Please advise.
-- OPTION #1
SET NOCOUNT OFF
IF OBJECT_ID('tempdb..#Table') IS NOT NULL
BEGIN
DROP TABLE #Table
END
-- select count(*) from #Table where ID = 0
-- select * from #Table
CREATE TABLE #Table ( ID INT )
WHILE (1 = 1)
AND ( Select count(*) from #Table ) < 10000
BEGIN
BEGIN TRANSACTION
INSERT INTO #Table (ID)
VALUES (1)
IF ##ROWCOUNT = 10000 -- terminating condition;
BEGIN
COMMIT TRANSACTION
BREAK
END
END
-- UPDATE
WHILE (1 = 1)
BEGIN
BEGIN TRANSACTION
UPDATE TOP (10) upd
SET ID = 0
FROM #Table upd
IF ##ROWCOUNT = 0 -- terminating condition;
BEGIN
COMMIT TRANSACTION
BREAK
END
END
-- OPTION #2
SET NOCOUNT OFF
IF OBJECT_ID('tempdb..#Table2') IS NOT NULL
BEGIN
DROP TABLE #Table2
END
-- select count(*) from #Table2 where ID = 0
-- select * from #Table2
CREATE TABLE #Table2 ( ID INT )
--DECLARE #rows INT
--DECLARE #count INT
WHILE (1 = 1)
AND ( Select count(*) from #Table2 ) < 10000
BEGIN
BEGIN TRANSACTION
INSERT INTO #Table2 (ID)
VALUES (1)
IF ##ROWCOUNT = 10000 -- terminating condition;
BEGIN
COMMIT TRANSACTION
BREAK
END
END
DECLARE #rows INT
DECLARE #count INT
-- UPDATE
SET #rows = 1
SET #count = 0
WHILE #rows > 0
BEGIN
BEGIN TRANSACTION
UPDATE TOP (10) #Table2 -- upd
SET ID = 0
-- FROM #Table upd
SET #rows = ##ROWCOUNT
SET #count = #count + #rows
RAISERROR('COUNT %d', 0, 1, #count) WITH NOWAIT
COMMIT TRANSACTION
END
OK there were a couple of issues with your code.
You can't use TOP in an update - however its fairly straight forward to restrict the rows with a sub-query as shown.
You were setting all the ID's to 1 therefore there was no way to uniquely identify a row, you could only update all of them. I have assumed that in your real life problem you would have unique ID's and I have modified the code to suit.
I'm unsure about the intention of the various nested transactions, they don't appear to accomplish much and they don't match the logic.
IF OBJECT_ID('tempdb..#Table2') IS NOT NULL
BEGIN
DROP TABLE #Table2;
END
CREATE TABLE #Table2 (ID INT);
DECLARE #Count int = 0;
WHILE (select count(*) from #Table2) < 10000 BEGIN
INSERT INTO #Table2 (ID)
VALUES (#Count)
-- Make sure we have a unique id for the test, else we can't identify 10 records
set #Count = #Count + 1;
END
-- While exists an 'un-updated' record continue
WHILE exists (select 1 from #Table2 where ID > 0) BEGIN
-- Update any top 10 'un-updated' records
UPDATE #Table2 SET
ID = 0
where id in (select top 10 id from #Table2 where ID > 0)
END
DROP TABLE #Table2
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;
I was wondering if anyone could help me with my SQL code below. I am trying to insert all of the records from my BC_completions table into my Geodata table. If I exclude the ID column on the insert, I get a 'Msg 515... Cannot insert the value NULL into column 'ID'. But if I include it as below I get a 'Msg ...Violation of PRIMARY KEY constraint'.
What makes it more confusing is that if I manually typed in the value I get from #PK the database accepts it, so any help here would be great.
Thanks
DECLARE #PK AS INT
DECLARE #COUNT AS INT
DECLARE #RECORDCOUNT AS INT
SET #PK =
(SELECT TOP 1 ID FROM PRCORE.DBO.GEODATA
ORDER BY ID DESC)
SET #RECORDCOUNT =
(SELECT COUNT(*) FROM BC_COMPLETIONS)
SET #COUNT = 0
WHILE #COUNT < #RECORDCOUNT
BEGIN
SET #PK = #PK+1
SET #COUNT = #COUNT+1
INSERT INTO PRCORE.DBO.GEODATA
(ID,RecordType,ReferenceName,LocationDescription,Description,ORN,StartDate,ChgText)
SELECT #PK,189,REFVAL,ADDRESS,DSCRPN,ORN,RECPTD,AGTNAME
FROM BC_COMPLETIONS B
where #PK not in (select ID from prcore.dbo.geodata)
END
This is an issue with your loop and your WHERE constraint for the insert statement. You're selecting all of the records from BC_COMPLETIONS and assigning them to the same PK.
Instead, use the ROW_NUMBER() function to assign your PK which will allow you to do this all at once instead of one record at a time:
DECLARE #PK AS INT
DECLARE #RECORDCOUNT AS INT
SET #PK = (SELECT TOP 1 ID FROM PRCORE.DBO.GEODATA ORDER BY ID DESC) + 1
SET #RECORDCOUNT = (SELECT COUNT(*) FROM BC_COMPLETIONS)
INSERT INTO PRCORE.DBO.GEODATA (ID,RecordType,ReferenceName,LocationDescription,Description,ORN,StartDate,ChgText)
SELECT ROW_NUMBER() OVER(ORDER BY REFVAL) + #PK ,189,REFVAL,ADDRESS,DSCRPN,ORN,RECPTD,AGTNAME
FROM BC_COMPLETIONS B
How do I loop through a set of records from a select statement?
Say I have a few records that I wish to loop through and do something with each record. Here's a primitive version of my select statement:
select top 1000 * from dbo.table
where StatusID = 7
By using T-SQL and cursors like this :
DECLARE #MyCursor CURSOR;
DECLARE #MyField YourFieldDataType;
BEGIN
SET #MyCursor = CURSOR FOR
select top 1000 YourField from dbo.table
where StatusID = 7
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyField
WHILE ##FETCH_STATUS = 0
BEGIN
/*
YOUR ALGORITHM GOES HERE
*/
FETCH NEXT FROM #MyCursor
INTO #MyField
END;
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END;
This is what I've been doing if you need to do something iterative... but it would be wise to look for set operations first. Also, do not do this because you don't want to learn cursors.
select top 1000 TableID
into #ControlTable
from dbo.table
where StatusID = 7
declare #TableID int
while exists (select * from #ControlTable)
begin
select top 1 #TableID = TableID
from #ControlTable
order by TableID asc
-- Do something with your TableID
delete #ControlTable
where TableID = #TableID
end
drop table #ControlTable
Small change to sam yi's answer (for better readability):
select top 1000 TableID
into #ControlTable
from dbo.table
where StatusID = 7
declare #TableID int
while exists (select * from #ControlTable)
begin
select #TableID = (select top 1 TableID
from #ControlTable
order by TableID asc)
-- Do something with your TableID
delete #ControlTable
where TableID = #TableID
end
drop table #ControlTable
By using cursor you can easily iterate through records individually and print records separately or as a single message including all the records.
DECLARE #CustomerID as INT;
declare #msg varchar(max)
DECLARE #BusinessCursor as CURSOR;
SET #BusinessCursor = CURSOR FOR
SELECT CustomerID FROM Customer WHERE CustomerID IN ('3908745','3911122','3911128','3911421')
OPEN #BusinessCursor;
FETCH NEXT FROM #BusinessCursor INTO #CustomerID;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #msg = '{
"CustomerID": "'+CONVERT(varchar(10), #CustomerID)+'",
"Customer": {
"LastName": "LastName-'+CONVERT(varchar(10), #CustomerID) +'",
"FirstName": "FirstName-'+CONVERT(varchar(10), #CustomerID)+'",
}
}|'
print #msg
FETCH NEXT FROM #BusinessCursor INTO #CustomerID;
END
Just another approach if you are fine using temp tables.I have personally tested this and it will not cause any exception (even if temp table does not have any data.)
CREATE TABLE #TempTable
(
ROWID int identity(1,1) primary key,
HIERARCHY_ID_TO_UPDATE int,
)
--create some testing data
--INSERT INTO #TempTable VALUES(1)
--INSERT INTO #TempTable VALUES(2)
--INSERT INTO #TempTable VALUES(4)
--INSERT INTO #TempTable VALUES(6)
--INSERT INTO #TempTable VALUES(8)
DECLARE #MAXID INT, #Counter INT
SET #COUNTER = 1
SELECT #MAXID = COUNT(*) FROM #TempTable
WHILE (#COUNTER <= #MAXID)
BEGIN
--DO THE PROCESSING HERE
SELECT #HIERARCHY_ID_TO_UPDATE = PT.HIERARCHY_ID_TO_UPDATE
FROM #TempTable AS PT
WHERE ROWID = #COUNTER
SET #COUNTER = #COUNTER + 1
END
IF (OBJECT_ID('tempdb..#TempTable') IS NOT NULL)
BEGIN
DROP TABLE #TempTable
END
You could choose to rank your data and add a ROW_NUMBER and count down to zero while iterate your dataset.
-- Get your dataset and rank your dataset by adding a new row_number
SELECT TOP 1000 A.*, ROW_NUMBER() OVER(ORDER BY A.ID DESC) AS ROW
INTO #TEMPTABLE
FROM DBO.TABLE AS A
WHERE STATUSID = 7;
--Find the highest number to start with
DECLARE #COUNTER INT = (SELECT MAX(ROW) FROM #TEMPTABLE);
DECLARE #ROW INT;
-- Loop true your data until you hit 0
WHILE (#COUNTER != 0)
BEGIN
SELECT #ROW = ROW
FROM #TEMPTABLE
WHERE ROW = #COUNTER
ORDER BY ROW DESC
--DO SOMTHING COOL
-- SET your counter to -1
SET #COUNTER = #ROW -1
END
DROP TABLE #TEMPTABLE
this way we can iterate into table data.
DECLARE #_MinJobID INT
DECLARE #_MaxJobID INT
CREATE TABLE #Temp (JobID INT)
INSERT INTO #Temp SELECT * FROM DBO.STRINGTOTABLE(#JobID,',')
SELECT #_MinJID = MIN(JobID),#_MaxJID = MAX(JobID) FROM #Temp
WHILE #_MinJID <= #_MaxJID
BEGIN
INSERT INTO Mytable
(
JobID,
)
VALUES
(
#_MinJobID,
)
SET #_MinJID = #_MinJID + 1;
END
DROP TABLE #Temp
STRINGTOTABLE is user define function which will parse comma separated data and return table. thanks
I think this is the easy way example to iterate item.
declare #cateid int
select CateID into [#TempTable] from Category where GroupID = 'STOCKLIST'
while (select count(*) from #TempTable) > 0
begin
select top 1 #cateid = CateID from #TempTable
print(#cateid)
--DO SOMETHING HERE
delete #TempTable where CateID = #cateid
end
drop table #TempTable
I have a trigger ,but I need to get the updated record's primary key (like as inserting the data SELECT #Id= ##IDENTITY) thus, I can pass it to where condition. How can I do that?
ALTER TRIGGER [dbo].[CariBakiyeBorcAktar]
ON [dbo].[BakimKartiDegisenParcalar]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Id int
DECLARE #CariId int
DECLARE #SId int
DECLARE #MId int
declare #Tutar decimal
declare #Bakiye decimal
declare #s decimal = 0
DECLARE #ParcaId int
--how I can I get the last updateed record Identity like this??
--and pass it to update query as a where condition
SELECT #Id= ##IDENTITY
set #SId=(select SId from CariBakiye where Id =#Id)
select #CariId=tblk.CariId ,#MId=tblk.MId, #SId= tblk.SId,#Tutar=tblk.Tutar from (
SELECT tbl.CariId , tbl.MId,tbl.SId,tbl.Tutar from (select cb.MId,SUM(bk.Tutar) as Tutar,bk.SId,cb.Id as CariId FROM [BakimKartiDegisenParcalar] bk
join CariBakiye cb on cb.SId=bk.SId
where bk.SId =cb.SId group by bk.SId,cb.MId,cb.Id ) as tbl
) as tblk where SId = #SId
set #Bakiye = #s-#Tutar
update CariBakiye set Borc=#Tutar,Bakiye=#Bakiye where Id=#CariId
print #Id
-- Insert statements for trigger here
END
As Martin said, you have to understand that SQL Server triggers are per statement, not per row. So in context of your trigger you have two tables - inserted and deleted, where you could find all information about data updated. If you really want to do per row processing, you could use cursor:
ALTER TRIGGER [dbo].[CariBakiyeBorcAktar] ON [dbo].[BakimKartiDegisenParcalar]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Id int
DECLARE #CariId int
DECLARE #SId int
DECLARE #MId int
declare #Tutar decimal
declare #Bakiye decimal
declare #s decimal = 0
DECLARE #ParcaId int
declare tr_cursor cursor local fast_forward for
select ID from inserted
while 1 = 1
begin
fetch tr_cursor into #Id
if ##fetch_status <> 0 break
set #SId=(select SId from CariBakiye where Id =#Id)
select #CariId=tblk.CariId ,#MId=tblk.MId, #SId= tblk.SId,#Tutar=tblk.Tutar from (
SELECT tbl.CariId , tbl.MId,tbl.SId,tbl.Tutar from (select cb.MId,SUM(bk.Tutar) as Tutar,bk.SId,cb.Id as CariId FROM [BakimKartiDegisenParcalar] bk
join CariBakiye cb on cb.SId=bk.SId
where bk.SId =cb.SId group by bk.SId,cb.MId,cb.Id ) as tbl
) as tblk where SId = #SId
set #Bakiye = #s-#Tutar
update CariBakiye set Borc=#Tutar,Bakiye=#Bakiye where Id=#CariId
print #Id
-- Insert statements for trigger here
end
close tr_cursor
deallocate tr_cursor
END