Update identity column in dynamic T-SQL - sql

I should write a SQL script in which I use a dynamic cursor. I want to reidentity table rows and has written stored procedure:
CREATE PROCEDURE [dbo].[ReidentityComments]
AS
BEGIN
SET NOCOUNT ON;
DECLARE Reidentitier CURSOR
LOCAL
SCROLL
DYNAMIC
FOR
SELECT * FROM Comment
FOR UPDATE;
OPEN Reidentitier;
DECLARE #CommentId INT;
DECLARE #FilmId INT;
DECLARE #Text NVARCHAR(2000);
DECLARE #PlacingDate DATETIME;
DECLARE #UserId INT;
DECLARE #current INT;
SET #current = 1;
DECLARE #updateSql NVARCHAR(100);
SET #updateSql = N'
SET IDENTITY_INSERT VideoLibrary.dbo.Comment ON;
UPDATE Comment SET CommentId = #cur WHERE CommentId = #id;
SET IDENTITY_INSERT VideoLibrary.dbo.Comment OFF;
';
DECLARE #params NVARCHAR(100);
SET #params = N'#cur INT, #id INT';
FETCH NEXT
FROM Reidentitier
INTO #CommentId, #FilmId, #Text, #PlacingDate, #UserId;
WHILE ##FETCH_STATUS = 0
BEGIN
IF #CommentId != #current
EXECUTE sp_executesql #updateSql, #params, #cur = #current, #id = #CommentId;
FETCH NEXT
FROM Reidentitier
INTO #CommentId, #FilmId, #Text, #PlacingDate, #UserId;
SET #current = #current + 1;
END
END
But when I try to execute the procedure I get the error:
An expression of non-boolean type specified in a context where a condition is expected, near 'Co'.
Can anyone help me, please?

The problem in your code is in the declaration of #updateSql
nvarchar(100) is too small to store the whole string. It's getting truncated, and you're ending up with something like...
SET IDENTITY_INSERT VideoLibrary.dbo.Comment ON;
UPDATE Comment SET CommentId = #cur WHERE CommentId
And that's when the error is returned...
That said, i.m.h.o. a better approach to do what you're trying to do would probably be:
SELECT IDENTITY (int, 1, 1) AS CommentId, FilmId, Text, PlacingDate, UserId
INTO TMP_COMMENT
FROM Comment;
exec SP_RENAME Comment, OLD_COMMENT;
exec SP_RENAME TMP_COMMENT, Comment;
The only downside to this approach is that none of the indexes, or primary keys will be created on the new comment table.

You wont be able to update the identity column anyway (regardless of Identity_insert value).
So to solve your problem - Does it really need to be the identity column? (if the Identity column is also the primary key you could get into issues if you have data referring to the comments table.)
Could you just create a column called RowNum (for instance) - and re populate that using Row_Number()?
eg
UPDATE Comment SET RowNum = c.NewSequence
FROM (
SELECT CommentId, NewSequence = ROW_NUMBER() OVER (ORDER BY CommentId)
FROM Comments) c
WHERE c.Id = Comment.id

Related

I have a trigger on my SQL Server table which takes user updates and log them, in case we need to revert, but it has a problem

The problem is, sometimes in a day that no one is changing anything, a random user just enter the page and the trigger saves a change. The problem is, not only it logs a change that never has occurred in that day/moment(Because he/she didn't made a change), but it also gets a random data from INSERTED/DELETED, like we have a log of a change on may 5 2019 that has the date of change set in 2014, which is a long time ago.
My trigger is similar to this one below, just without personal information. We simulated this problem by making changes on a day, then trigger logs it correctly, after that we change the date on our computer, log in and wait a little bit, than it logs something random. Sometimes it takes a lot of time, and enter/exiting pages, but eventually something completely random appears from another date from long ago. Thanks for the help!
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[tablelog]
ON [dbo].[tablechanged]
AFTER UPDATE
AS
BEGIN
declare #OLD_DATA nvarchar(2000);
declare #NEW_DATA nvarchar(2000);
declare #Counter INT;
declare #Occurrences INT;
declare #col varchar(1000);
declare #SELECDELET nvarchar(2000);
declare #SELECINSER nvarchar(2000);
declare #user varchar(50);
declare #cod int;
declare #emp INT;
declare #isReg bit;
set #Occurrences = (SELECT COUNT(COLUMN_NAME) FROM information_schema.columns WHERE table_name = 'tablechanged')
set #Counter = 0;
set #user = (SELECT TOP 1 usuarioUltimaAlteracao FROM INSERTED);
set #emp = (SELECT TOP 1 empCodigo FROM INSERTED);
set #cod = (SELECT TOP 1 cedCodigo FROM INSERTED);
set #isReg = (SELECT TOP 1 alteracaoViaCadastro FROM INSERTED);
SELECT * INTO #Del FROM DELETED
SELECT * INTO #Ins FROM INSERTED
if(#isReg = 1)
begin
while #Counter < #Occurrences
begin
set #Counter = #Counter + 1;
set #col = (select COLUMN_NAME FROM information_schema.columns WHERE table_name = 'tablechanged' and ordinal_position = #Counter);
select #SELECDELET = 'SELECT #OLD_DATA='+#col+' FROM #Del' ;
select #SELECINSER = 'SELECT #NEW_DATA='+#col+' FROM #Ins' ;
exec sp_executesql #SELECDELET, N'#OLD_DATA nvarchar(40) OUTPUT', #OLD_DATA OUTPUT
exec sp_executesql #SELECINSER, N'#NEW_DATA nvarchar(40) OUTPUT', #NEW_DATA OUTPUT
if(#OLD_DATA <> #NEW_DATA)
begin
INSERT INTO TABLELOG (OPE_DATE,OPE_USER,OPE_TABLE,OPE_COD,OPE_EMP,OPE_FIELD,OPE,OLD_DATA,NEW_DATA)
VALUES (getdate(), #user, 'tablechanged', #cod, #emp, #col, 'UPDATE', #OLD_DATA,#NEW_DATA)
end
end
end
END
SQL Server triggers fire for every statement. Not for every row. Your trigger is obviously broken for the case of a multi-row update.
In the case of a multi-row update, the value of #NEW_DATA after running
SELECT #NEW_DATA='+#col+' FROM #Ins' ;
will be the last value in #Ins, and without an ORDER BY, it's undocumented which row it come from.

SQL Server trigger with loop for multiple row insertion

I've created trigger for my database which handles some insertion but when I add multiple values in 1 SQL query it doesn't work:
ALTER TRIGGER [dbo].[ConferenceDayTrigger]
ON [dbo].[Conferences]
AFTER INSERT
AS
BEGIN
DECLARE #ID INT
DECLARE #dayC INT
DECLARE #counter INT
SET #counter = 1
SET #ID = (SELECT IDConference FROM Inserted)
SET #dayC = (SELECT DATEDIFF(DAY, start,finish) FROM Inserted)
WHILE #counter <= #dayC + 1
BEGIN
EXEC AddConferenceDay #Id, #counter
SET #counter = #counter +1
END
END
For single insertion it works ok. But what should I change/add to make it execute for each row of inserted values?
If you cannot change the stored procedure, then this might be one of the (very few) cases when a cursor comes to the rescue. Double loops, in fact:
ALTER TRIGGER [dbo].[ConferenceDayTrigger]
ON [dbo].[Conferences]
AFTER INSERT
AS
BEGIN
DECLARE #ID INT;
DECLARE #dayC INT;
DECLARE #counter INT
SET #counter = 1;
DECLARE yucky_Cursor CURSOR FOR
SELECT IDConference, DATEDIFF(DAY, start,finish) FROM Inserted;
OPEN yucky_Cursor; /*Open cursor for reading*/
FETCH NEXT FROM yucky_Cursor INTO #ID, #dayC;
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #counter <= #dayC + 1
BEGIN
EXEC AddConferenceDay #Id, #counter;
SET #counter = #counter + 1;
END;
FETCH NEXT FROM yucky_Cursor INTO #ID, #dayC;
END;
CLOSE yucky_Cursor;
DEALLOCATE yucky_Cursor;
END;
I suspect there is a way to refactor and get rid of the cursor and use set-based operations.
When you insert more than one record, you need to cursor/while to call the AddConferenceDay procedure for each record.
But I will suggest you to alter your procedure to accept table type as input parameter. So that more than one ID and dayC as input to AddConferenceDay procedure. It is more efficient than your current approach.
something like this
create type udt_Conferences as table (ID int,dayC int)
Alter the procedure to use udt_Conferences as input parameter
Alter procedure AddConferenceDay (#input udt_Conferences readonly)
as
begin
/* use #input table type instead of #Id and #counter variables */
end
To call the procedure update the trigger with created udt
ALTER TRIGGER [dbo].[ConferenceDayTrigger]
ON [dbo].[Conferences]
AFTER INSERT
AS
BEGIN
Declare #input udt_Conferences
insert into #input (ID,dayC)
select IDConference,DATEDIFF(DAY, start,finish) from Inserted
END
add these lines to your trigger
AFTER INSERT
AS
BEGIN
AFTER INSERT
AS
BEGIN
Declare #Count int;
Set #Count=##ROWCOUNT;
IF #Count=0
Return;
SET NOCOUNT ON;
-- Insert statements for trigger here

Build select statement dynamically and use it to populate a stored procedure variable

I am trying to get count of a rows with specific values in a table, and if the count is 0, then add a value in the table. This count is a local variable in stored procedure.
I am building the SQL dynamically and storing SQL statement into a nvarchar variable.
Then, using EXEC I am running this SQL as follows hoping to populate count variable.
But it's not working.
DECLARE #qry NVARCHAR(max)
DECLARE #count INT
-- building #qry will result as follows
#qry = SELECT #count = COUNT(*) FROM aTable WHERE (col1 = #col1 AND ...)
#count = EXEC #qry
IF #count = 0
BEGIN
-- carry on with adding
END
In your sql ,why you are execute your query through EXEC because of your required output is already in #count variable so it is not need in your case.
Please refer below syntax.
DECLARE #qry Numeric
DECLARE #count INT
-- building #qry will result as follows
SELECT #count = COUNT(*) FROM aTable WHERE (col1 = #col1 AND ...)
IF #count = 0
BEGIN
-- carry on with adding
END
If you are building the query dynamically, you need sp_executesql. Try something like
-- building #qry will result as follows
#qry = 'SELECT #count = COUNT(*) FROM aTable WHERE (col1 = #col1 AND ...)'
EXEC sp_executesql #qry, N'#count INT OUTPUT', #count OUTPUT;
--Do whatever you want with #count...
Source: Aaron Bertrand's answer here and sp_executesql explanation..
I think #qry needs to be a string for executing, not the result of the select, like so:
DECLARE #qry NVARCHAR(max);
DECLARE #count INT;
-- building #qry will result as follows
SET #qry = 'SELECT COUNT(*) FROM aTable WHERE (col1 = #col1 AND ...)';
SET #count = exec #qry;

update trigger get the updated primary key

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

Is there a way to force a trigger to run on an update statement with multiple rows?

I have had to make changes to a trigger and assumed that running an update query like the following would make the trigger execute for all the matched rows. But instead, it only updates the record that it finds.
UPDATE someTable SET someField = someField WHERE someField = 'something';
As a quick solution, I created the following query using a cursor to loop through the records and update each row. It works, and luckily I don't have a really large dataset so it doens't take too long, but it just doesn't seem like the best solution.
DECLARE #id INT;
DECLARE queryCursor CURSOR FOR
SELECT id FROM someTable WHERE someField='something'
OPEN queryCursor
FETCH NEXT FROM queryCursor INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE someTable SET someField = someField WHERE id = #id
FETCH NEXT FROM queryCursor INTO #id
END
CLOSE queryCursor
DEALLOCATE queryCursor
Is there a better way to get a trigger to execute on multiple rows in SQL Server?
Edit: The code from trigger
FOR INSERT, UPDATE
AS
IF UPDATE (LineNumber)
OR UPDATE(LineService)
Begin
DECLARE #CDL VARCHAR(50)
DECLARE #LN VARCHAR(100)
DECLARE #A VARCHAR(25)
SELECT #CDL = CommonDataLink FROM INSERTED
SELECT #A = LineService FROM INSERTED
SET #LN = #CDL + #A
UPDATE CommonData SET ReportedLineNo = #LN WHERE CommonDataLink = #CDL
End
You have to make use of the special table INSERTED for what you want:
UPDATED CODE
FOR INSERT, UPDATE
AS
IF UPDATE (LineNumber)
OR UPDATE(LineService)
Begin
DECLARE #CDL VARCHAR(50)
DECLARE #LN VARCHAR(100)
DECLARE #A VARCHAR(25)
SELECT #CDL = CommonDataLink FROM INSERTED
SELECT #A = LineService FROM INSERTED
SET #LN = #CDL + #A
UPDATE A
SET ReportedLineNo = B.LineService + B.CommonDataLink
FROM CommonData A
INNER JOIN INSERTED B
ON A.CommonDataLink = B.CommonDataLink
End