Check how many changes were made by UPDATE - sql

I have infinite loop in which I update whole values from one column with select-generated column. I want to break the loop when number of changes in update is 0. Update returns number of rows so what I need is to compare before update and after update column.

I think you are looking for something like this :-
WHILE(1)
BEGIN
-- Some logic here ---
UPDATE HumanResources.Employee
SET JobTitle = N'Executive'
WHERE NationalIDNumber = 123456789
IF ##ROWCOUNT = 0
BREAK
END

You can use ##ROWCOUNT to get the number of rows that were affected by UPDATE. You can get the number of rows that were actually different by providing the UPDATE clause with a WHERE clause which excludes the rows that do not need to change. For example:
UPDATE Tbl1
SET col1 = col2
WHERE col1 != col2
IF ( ##RowCount == 0) BREAK

Related

MSSQL Update: output value before update

There is a table with IDU (PK) and stat columns. If first bit of stat is 1 I need to set it to 0 and run some stored procedure in this case only, otherwise I do nothing.
Here is the simple query for this
DECLARE #s INT
-- get the current value of status before update
SET #s = (SELECT stat FROM myTable
WHERE IDU = 999999999)
-- check it first bit is 1
IF (#s & 1) = 1
BEGIN
-- first bit is 1, set it to 0
UPDATE myTable
SET status = Stat & ~1
WHERE IDU = 999999999
-- first bit is one, in this case we run our SP
EXEC SOME_STORED_PROCEDURE
END
But I'm not sure that this query is optimal. I heard about OUTPUT parameter for UPDATE query but I found how to get inserted value. Is there a way to get a value that was before insert?
Yes, OUTPUT clause allows you to get the previous value before the update. You need to look at deleted and inserted tables.
DELETED
Is a column prefix that specifies the value deleted by the
update or delete operation. Columns prefixed with DELETED reflect the
value before the UPDATE, DELETE, or MERGE statement is completed.
INSERTED
Is a column prefix that specifies the value added by the insert or
update operation. Columns prefixed with INSERTED reflect the value
after the UPDATE, INSERT, or MERGE statement is completed but before
triggers are executed.
-- Clear the first bit without checking what it was
DECLARE #Results TABLE (OldStat int, NewStat int);
UPDATE myTable
SET Stat = Stat & ~1
WHERE IDU = 999999999
OUTPUT
deleted.Stat AS OldStat
,inserted.Stat AS NewStat
INTO #Results
;
-- Copy data from #Results table into variables for comparison
-- Assumes that IDU is a primary key and #Results can have only one row
DECLARE #OldStat int;
DECLARE #NewStat int;
SELECT #OldStat = OldStat, #NewStat = NewStat
FROM #Results;
IF #OldStat <> #NewStat
BEGIN
EXEC SOME_STORED_PROCEDURE;
END;
Regardless of optimal, this query is not 100% safe. This is because between SET #s =... and UPDATE myTable there is no guarantee the value of stat has not been changed. If this code runs multiple times it is possible to screw up if two cases execute deadly close for the same IDU. The first thread will do ok but the second one will not, since the first would change the stat after the second read it and before update it. A select statement does not lock beyond its own execution time even on SERIALIZABLE isolation.
To be safe, you need to lock the record BEFORE read it, and to do that you need an update statement, even fake:
DECLARE #s INT
BEGIN TRANSACTION
UPDATE myTable SET stat = stat WHERE IDU = 999999999 --now you row lock your row, make sure no other thread can move along
-- get the current value of status before update
SET #s = (SELECT stat FROM myTable
WHERE IDU = 999999999)
-- check it first bit is 1
IF (#s & 1) = 1
BEGIN
-- first bit is 1, set it to 0
UPDATE myTable
SET status = Stat & ~1
WHERE IDU = 999999999
-- first bit is one, in this case we run our SP
-- COMMIT TRANSACTION here? depends on what SOME_STORED_PROCEDURE does
EXEC SOME_STORED_PROCEDURE
COMMIT TRANSACTION --i believe here you release the row lock
I am not sure what you mean by "Is there a way to get a value that was before insert" because you only update and the only data, stat, you had already read from the old record regardless if you update or not.
You could do this with an INSTEAD OF UPDATE Trigger.

Multiple Steps on WHEN NOT MATCHED possible?

I have a sequence I need to use to recalculate both fields in the primary key if an update match isn't found. Is it possible to still use the MERGE statement here? I tried WHEN MATCHED THEN BEGIN, but BEGIN isn't valid here.
Specifically, I have a pair of numbers that make up the primary key. The first is a grouping, and the second is a sequence of items within the group. If something goes wrong, the group comes in as 99990, and I need to combine it with the sequence and use a sequence to increment, then split it back apart. So, when the group comes in with 99990, my calculated groups can range from 99990 through 99999, and the sequence number will then range from 00 through 99.
I can't think of a way to do this as part of the INSERT assignment, and I can't figure out how to make the MERGE do multiple steps, so I'm guessing I'm back to UPDATE, IF ##ROWCOUNT = 0 BEGIN. Anyone have a faster way to do this?
This works,
Declare a flag variable (bit) called #isinsert
Initialize it with the value of 1.
On the update clause set it to 0.
Like this: (assuming a table called table1 with a numeric 'id' field and an nvarchar 'field1' field).
declare #id numeric(18,0) -- That's the he lookup key
set #id=999 -- We use id as the search key
-- You can use any other field
declare #field nvarchar(50)
set #field = 'insert or update value(s)' -- This is the new value
declare #isinsert bit -- This is a flag that will
set #isinsert=1 -- indicate whether an insert or
-- an update were performed
MERGE table1 AS target
USING (SELECT #field) AS source (field1)
ON (target.id = #id)
WHEN MATCHED THEN
UPDATE SET field1 = source.field1
,#isinsert = 0 -- Set #isinsert to 0 on updates
WHEN NOT MATCHED THEN
INSERT (field1)
VALUES (source.field1);
if (#isinsert=1) print concat('inserted record at id: ',##IDENTITY)
else print concat('updated record at id: ',#id)
Unfortunately, it isn't possible to do multiple steps in a MERGE. Sometimes the new Common Table Expression syntax can be used, but I resorted to an UPDATE, IF ##ROWcOUNT = 0, INSERT so I could do multiple steps on the insert.

getting number of records updated or inserted in sql server stored procedure

I have an SP that inserts some records and updates others and deletes some. What I want is to return the count values of what was inserted and what was updated and what was deleted. I thought I could use ##ROWCOUNT but that is always giving me a 1.
After my INSERT I run:
PRINT ##ROWCOUNT
But my message console shows what really happened and this number:
(36 row(s) affected)
1
So I can see that 36 records were actually updated but ##ROWCOUNT returned a 1.
I am trying to do the same thing after the UPDATE and DELETE parts of the SP runs with the same result.
##ROWCOUNT will show the number of rows affected by the most recent statement - if you have any statements between the INSERT and the PRINT then it will give you the wrong number.
Can you show us a little more code so we can see the order of execution?
Depending on how #ninesided's answer works for you, you could also use the output clause on each update/insert/delete and get the counts from there.
Example:
declare #count table
(
id int
)
update mytable
set oldVal = newVal
output inserted.field1 into #count
select count(*) from #count
You could reuse the count table throughout, and set variables as needed to hold the values.

Whats Select '1' for in the following stored proceedure

BEGIN
IF EXISTS(SELECT * FROM Table1 WHERE ID=#ID)
BEGIN
UPDATE Table1 SET Name=#Name WHERE ID=#ID
SELECT '1'
END
ELSE
SELECT '0'
END
Is this the row no. of the table or what ?
Also "IF EXISTS" is checking what ? the table or if the ID exists or not ??
It looks like whoever wrote that Stored Procedure is using that as a return value to indicate success or failure.
Doing things that way will result in a single row with a single column being returned for each call to the procedure.
The correct way to handle this would be to actually use the return value of the stored procedure, rather than returning the single column single row:
BEGIN
IF EXISTS(SELECT * FORM Table1 WHERE ID = #ID)
BEGIN
UPDATE Table1 SET Name = #Name WHERE ID = #ID
RETURN 1
END
RETURN 0
END
The IF EXISTS is checking if there is a row in Table1 with the given ID. If there is a row it will update that row with the given name. The Select "1" will return "1" and Select "0" returns "0". The "1" or "0" would indicate if the row was found or not.
Presumably some calling code checks this value to determine if a row was updated or not.
Rather than checking and updating (two table accesses) you might as well do this.
UPDATE Table1 SET Name=#Name WHERE ID=#ID
SELECT CASE WHEN ##Rowcount = 0 THEN 0 ELSE 1 END
If id is the PK then you can just do
UPDATE Table1 SET Name=#Name WHERE ID=#ID
SELECT ##Rowcount
Note as long as SET NOCOUNT is not on then the number of rows affected will get passed back to the client application anyway.
Select '1' is used to indicate that Table1 contains the id value #ID (a parameter) was updated. Select '0' indicates that Table1 does not contain the id value #ID.

IF UPDATE() in SQL server trigger

If there's:
IF UPDATE (col1)
...in the SQL server trigger on a table, does it return true only if col1 has been changed or been updated?
I have a regular update query like
UPDATE table-name
SET col1 = 'x',
col2 = 'y'
WHERE id = 999
Now what my concern is if the "col1" was 'x' previously then again we updated it to 'x'
would IF UPDATE ("col1") trigger return True or not?
I am facing this problem as my save query is generic for all columns, but when I add this condition it returns True even if it's not changed...So I am concerned what to do in this case if I want to add condition like that?
It returns true if a column was updated. An update means that the query has SET the value of the column. Whether the previous value was the same as the new value is largely irelevant.
UPDATE table SET col = col
it's an update.
UPDATE table SET col = 99
when the col already had value 99 also it's an update.
Within the trigger, you have access to two internal tables that may help. The 'inserted' table includes the new version of each affected row, The 'deleted' table includes the original version of each row. You can compare the values in these tables to see if your field value was actually changed.
Here's a quick way to scan the rows to see if ANY column changed before deciding to run the contents of a trigger. This can be useful for example when you want to write a history record, but you don't want to do it if nothing really changed.
We use this all the time in ETL importing processes where we may re-import data but if nothing really changed in the source file we don't want to create a new history record.
CREATE TRIGGER [dbo].[TR_my_table_create_history]
ON [dbo].[my_table] FOR UPDATE AS
BEGIN
--
-- Insert the old data row if any column data changed
--
INSERT INTO [my_table_history]
SELECT d.*
FROM deleted d
INNER JOIN inserted i ON i.[id] = d.[id]
--
-- Use INTERSECT to see if anything REALLY changed
--
WHERE NOT EXISTS( SELECT i.* INTERSECT SELECT d.* )
END
Note that this particular trigger assumes that your source table (the one triggering the trigger) and the history table have identical column layouts.
What you do is check for different values in the inserted and deleted tables rather than use updated() (Don't forget to account for nulls). Or you could stop doing unneeded updates.
Trigger:
CREATE TRIGGER boo ON status2 FOR UPDATE AS
IF UPDATE (id)
BEGIN
SELECT 'DETECT';
END;
Usage:
UPDATE status2 SET name = 'K' WHERE name= 'T' --no action
UPDATE status2 SET name = 'T' ,id= 8 WHERE name= 'K' --detect
To shortcut the "No actual update" case, you need also check at the beginning whether your query affected any rows at all:
set nocount on; -- this must be the first statement!
if not exists (select 1 from inserted) and not exists (select 1 from deleted)
return;
SET NOCOUNT ON;
declare #countTemp int
select #countTemp = Count (*) from (
select City,PostCode,Street,CountryId,Address1 from Deleted
union
select City,PostCode,Street,CountryId,Address1 from Inserted
) tempTable
IF ( #countTemp > 1 )
Begin
-- Your Code goes Here
End
-- if any of these "City,PostCode,Street,CountryId,Address1" got updated then trigger
-- will work in " IF ( #countTemp > 1 ) " Code)
This worked for me
DECLARE #LongDescDirty bit = 0
Declare #old varchar(4000) = (SELECT LongDescription from deleted)
Declare #new varchar(4000) = (SELECT LongDescription from inserted)
if (#old <> #new)
BEGIN
SET #LongDescDirty = 1
END
Update table
Set LongDescUpdated = #LongDescUpdated
.....