What's the best way to lock a record while it is being updated? - sql

If I need to SELECT a value from a table column (happens to be the primary key column) based on a relatively complex WHERE clause in the stored procedure, and I then want to update that record without any other concurrent stored procedures SELECTing the same record, is it as simple as just using a transaction? Or do I also need to up the isolation to Repeatable Read?
It looks like this:
Alter Procedure Blah
As
Declare #targetval int
update table1 set field9 = 1, #targetval = field1 where field1 = (
SELECT TOP 1 field1
FROM table1 t
WHERE
(t.field2 = 'this') AND (t.field3 = 'that') AND (t.field4 = 'yep') AND (t.field9 <> 1))
return
I then get my targetval in my program so that I can do work on it, and meanwhile I don't have to worry about other worker threads grabbing the same targetval.
I'm talking SQL 2000, SQL 2005, and SQL 2008 here.

Adding ROWLOCK,UPDLOCK to the sub query should do it.
ALTER PROCEDURE Blah
AS
DECLARE #targetval INT
UPDATE table1
SET field9 = 1,
#targetval = field1
WHERE field1 = (SELECT TOP 1 field1
FROM table1 t WITH (rowlock, updlock)
WHERE ( t.field2 = 'this' )
AND ( t.field3 = 'that' )
AND ( t.field4 = 'yep' )
AND ( t.field9 <> 1 ))
RETURN
Updated
The currently accepted answer to this question does not use updlock. I'm not at all convinced that this will work. As far as I can see from testing in this type of query with a sub query SQL Server will only take S locks for the sub query. Sometimes however the sub query will get optimised out so this approach might appear to work as in Query 2.
Test Script - Setup
CREATE TABLE test_table
(
id int identity(1,1) primary key,
col char(40)
)
INSERT INTO test_table
SELECT NEWID() FROM sys.objects
Query 1
update test_table
set col=NEWID()
where id=(SELECT top (1) id from test_table )
Query 2
update test_table
set col=NEWID()
where id=(SELECT max(id) from test_table)

Related

Automation Anywhere SQL results

I am trying to capture if my SQL Query have 0 rows or multiple rows. If it has 0 rows then I will insert, if 1 will perform an update, if > 1 will perform additional analysis.
Is there a way I can see if my query resulted in x results or no results in automation anywhere?
Any assistance will be appreciated.
You can make use of if exists and if not exists and check if rows exists or not, or even if there are multiple before doing the insert.
Here is a simple example using if not exists where if the row doesn't exist on dbo.Table it will insert a row. If it already exists then the ID will be logged to an Error table.
declare #InsertID int = 5, #Name nvarchar(max) = 'some name'
if ((select count(1) from dbo.Table where ID = #InsertID) > 1) -- detect error; more than one record for an id
begin
insert into dbo.Error (ErrorID, ErrorDate)
select #InsertID, getdate()
end
else if not exists (select 1 from dbo.Table where ID = #InsertID) -- no record exists for ID, insert it
begin
insert into dbo.Table (ID, Name)
select #InsertID, #Name
else if exists (select 1 from dbo.Table where ID = #InsertID) -- update the single record
begin
update dbo.Table set Name = #Name where ID = #InsertID
end
A2019 returns the results of a SQL Query as a table...
You could have an if statement right after your query which checks to see if the row count of the returned table is > 0 then take action accordingly.

Use NULL as a valid value in a stored procedure

I have a stored procedure with one parameter, #ID, which is an integer that might be zero. When it is zero, I want to use it as if it is null. So here is how I have written my query:
If #ID = 0
SELECT * FROM MyTable WHERE ID IS NULL
ELSE
SELECT * FROM MyTable WHERE ID = #ID;
This is quite inelegant. Surely there is a way to write the WHERE clause in such a way that makes duplicating the SELECT statement unnecessary.
You can phrase this more simply using:
SELECT *
FROM MyTable
WHERE COALESCE(ID, 0) = #id;
Next, you probably do not want to do this. It will prevent SQL Server from using an index. Similarly, OR is likely to prevent optimization as well.
Probably your best bet is your current code, or UNION ALL:
SELECT *
FROM MyTable
WHERE ID IS NULL AND #id = 0
UNION ALL
SELECT *
FROM MyTable
WHERE ID = #ID; -- not sure if `#id <> 0` is needed here
With this or your approach, you probably need OPTION (RECOMPILE) to ensure that an index is always used.
Just combine them using AND/OR logic:
SELECT *
FROM MyTable
WHERE (#Id != 0 AND ID = #ID)
OR (#Id = 0 AND ID IS NULL);

MS SQL Execute multiple updates based on a list

I have the problem that I found out how to fix the database the only problem is that I have to insert the CaseNumber for one execution everytime.
In C# I would use somekind of string list for the broken records is there something in MS SQL.
My Code so far I implemented a variable CaseNumber. I have a table with a lot of Casenumber records that are broken. Is there a way to execute this for every Casenumber of a different table.
Like:
1. Take the first casenumber and run this script.
2. Than take the second one and run this script again until every casenumber was fixed.
Thx in advance for any idea.
GO
DECLARE #CaseNumber VARCHAR(50)
SET #CaseNumber = '25615'
print 'Start fixing broken records.'
print 'Fixing FIELD2'
UPDATE t
SET t.FIELD2 = ( SELECT DISTINCT TOP 1 FIELD2
FROM {myTable} t2
WHERE IDFIELD = #CaseNumber
AND FIELD2 IS NOT NULL )
FROM {myTable} t
WHERE FIELD2 IS NULL
AND IDFIELD = #CaseNumber
</Code>
Here are a couple different options...
-- This verion will just "fix" everything that can be fixed.
UPDATE mt1 SET
mt1.FIELD2 = mtx.FIELD2
FROM
dbo.myTable mt1
CROSS APPLY (
SELECT TOP (1)
mt2.FIELD2
FROM
dbo.myTable mt2
WHERE
mt1.IDFIELD = mt2.IDFIELD
AND mt2.FIELD2 IS NOT NULL
) mtx
WHERE
mt1.FIELD2 IS NULL;
And if, for whatever reason, you don't want to fix the entire table all in one go. You can restrain to to just those you specify...
-- This version will works off the same principal but limits itself to only those values in the #CaseNumCSV parameter.
DECLARE #CaseNumCSV VARCHAR(8000) = '25615,25616,25617,25618,25619';
IF OBJECT_ID('tempdb..#CaseNum', 'U') IS NOT NULL
BEGIN DROP TABLE #CaseNum; END;
CREATE TABLE #CaseNum (
CaseNumber VARCHAR(50) NOT NULL,
PRIMARY KEY (CaseNumber)
WITH(IGNORE_DUP_KEY = ON) -- just in case the same CaseNumber is in the string multiple times.
);
INSERT #CaseNum(CaseNumber)
SELECT
CaseNumber = dsk.Item
FROM
dbo.DelimitedSplit8K(#CaseNumCSV, ',') dsk;
-- a copy of DelimitedSplit8K can be found here: http://www.sqlservercentral.com/articles/Tally+Table/72993/
UPDATE mt1 SET
mt1.FIELD2 = mtx.FIELD2
FROM
#CaseNum cn
JOIN dbo.myTable mt1
ON cn.CaseNumber = mt1.IDFIELD
CROSS APPLY (
SELECT TOP (1)
mt2.FIELD2
FROM
dbo.myTable mt2
WHERE
mt1.IDFIELD = mt2.IDFIELD
AND mt2.FIELD2 IS NOT NULL
) mtx
WHERE
mt1.FIELD2 IS NULL;

SQL Server - Stored Procedure Return in Case Result Expression

I'd like to use a return from a procedure in a CASE statement. It can't be a function because that procedure returns an inserted key.
UPDATE TIM
SET CD_LINHA_EVENTO =
CASE WHEN
TIM.CD_SUBESTRUTURA_PARAMETRO = (SELECT TOP 1 SPZ.CD_SUBESTRUTURA_PARAMETRO FROM SUBESTRUTURA_PARAMETRO SPZ WITH (NOLOCK) WHERE SPZ.CD_SUBESTRUTURA = TIM.CD_SUBESTRUTURA
AND SPZ.FL_SELECAO = 1 ORDER BY SPZ.NR_ORDEM)
THEN
**EXEC [dbo].[SPRTO_NumeracaoEventos]**
ELSE
(SELECT MAX(TIM2.CD_LINHA_EVENTO) FROM #TB_INSERTED_MODIFIED TIM2 WITH(NOLOCK))
END
FROM #TB_INSERTED_MODIFIED TIM WITH (NOLOCK)
stored procedure [SPRTO_NumeracaoEventos]:
INSERT INTO TB_NUMERACAO_EVENTOS (VALOR) VALUES ('')
RETURN SCOPE_IDENTITY()
Thank you!
I wasn't able to figure out your table schema well enough to provide a direct answer, but I put together a way to use inserted identity values within an update trigger. Hopefully you can adapt this to fit your specific use case.
Have a look at this SQL Fiddle
As noted in the comments in the fiddle, this strategy is based on using the OUTPUT clause to capture identity values into a table variable. See http://msdn.microsoft.com/en-us/library/ms177564.aspx for more on the OUTPUT clause.
For rows where the case is true, we insert one row per true case into the secondary table and capture the identities to a table variable, then we update the underlying table with a row number join between the inserted virtual table and the identity table variable. Then we run another update for the rows where the case is false. Here's the relevant trigger code from my fiddle example.
-- Create a table variable to hold identity ID values from OUTPUT clause
DECLARE #table_2_ids TABLE (id INT NOT NULL)
-- Insert to our secondary table where the condition is true and capture the identity values to table variable
INSERT table_2 (textval)
OUTPUT inserted.id INTO #table_2_ids
SELECT 'from trigger'
FROM inserted
WHERE flag = 1
-- Use row number to match up rows from inserted where the condition is true to the identity value table variable
-- Update matched identity id to underlying table
UPDATE t
SET table_2_id = r.id, textval = textval + ' and trigger inserted to table_2'
FROM table_1 t
JOIN (SELECT id, ROW_NUMBER() OVER (ORDER BY id) rn FROM inserted WHERE flag = 1) i ON i.id = t.id
JOIN (SELECT id, ROW_NUMBER() OVER (ORDER BY id) rn FROM #table_2_ids) r ON r.rn = i.rn
-- and now update where condition is false
UPDATE t
SET textval = textval + ' and trigger did not insert to table_2'
FROM table_1 t
WHERE flag = 0

Update and Insert Stored Procedure

I want to create a stored procedure that performs insert or update operation on a column if
that column does not contains a value that already exists in database it should allow insert when COUNT(field) = 0 or update when COUNT(field)=0 or 1 And I should know that either of these operation is performed or not.
Please solve my problem using COUNT not Exists because that won't work for UPDATE.
I am working in ASP.net - I have two columns of a table that are needed to be kept unique without using the unique constraint. So I want a procedure like this:
create proc usp_checkall #field1 varchar(20),
#field2 varchar(20),
#ID int,
#count int output
Now your query on updating/inserting #field1 & #field2 on basis of #id
If you happen to have SQL Server 2008, you could also try:
MERGE dbo.SomeTable AS target
USING (SELECT #ID, #Field_1, #Field_2) AS source (ID, Field_1, Field_2)
ON (target.ID = source.ID)
WHEN MATCHED THEN
UPDATE SET Field_1 = source.Field_1, Field_2 = source.Field_2
WHEN NOT MATCHED THEN
INSERT (ID, Field_1, Field_2)
VALUES (source.ID, source.Field_1, source.Field_2)
Use:
INSERT INTO your_table
(column)
VALUES
([ your_value ])
WHERE NOT EXISTS (SELECT NULL
FROM your_table
WHERE t.column = [ your_value ])
That will work on SQL Server, MySQL, Oracle, Postgres. All that's needed is to use the db appropriate variable reference. IE: For MySQL & SQL Server:
INSERT INTO your_table
(column)
VALUES
( #your_value )
WHERE NOT EXISTS (SELECT NULL
FROM your_table
WHERE t.column = #your_value)
To see if anything was inserted, get the value based on ##ROWCOUNT if using SQL Server. Use SQL%ROWCOUNT if you are using Oracle.
if Exists select * from Yourtable WHere Your Criteria
begin
update ...
end
else
begin
insert ...
end
This kind of approach will do the trick. #AlreadyExisted could be an OUTPUT parameter on the sproc for your calling code to check once it's returned.
DECLARE #AlreadyExisted BIT
SET #AlreadyExisted = 0
IF EXISTS(SELECT * FROM YourTable WHERE YourField = #FieldValue)
BEGIN
-- Record already exists
SET #AlreadyExisted = 1
UPDATE YourTable
SET....
WHERE YourField = #FieldValue
END
ELSE
BEGIN
-- Record does not already exist
INSERT YourTable (YourField,....) VALUES (#FieldValue,.....)
END