How to handle concurrency using SELECT? - sql-server-2008

I need to validate if a record already exists. The portion, in the stored procedure, that does that is:
SELECT EXISTS (SELECT 1 FROM MyTable
WHERE FirstName = #FirstName
AND LastName = #LastName
AND Address = #Address)
BEGIN
SET #IsNewRecord = 1
END
IF #IsNewRecord = 1
BEGIN
INSERT INTO MyTable
VALUES (#FirtName, #LastName, #Addres, #City, #Phone)
END
That works fine, the issue is when the stored procedure is called by several clients at the same time, it will return the #IsNewRecord = 1.
I already tried surrounding the validation among a TRANSACTION BLOCK, but it still creates a new record.
How can I handle concurrency when using SELECT?

do like this
begin transaction
SELECT EXISTS (SELECT 1 FROM MyTable WHERE FirstName = #FirstName AND LastName = #LastName
AND Address = #Address)
BEGIN
INSERT INTO MyTable
VALUES (#FirtName, #LastName, #Addres, #City, #Phone)
END
commit

Related

How stored procedure output instead of rows count?

My stored procedure always returns 0. I tried unique data and duplicated but the insert is done with success but the return value is always the same #new_identity = 0
CREATE PROCEDURE [dbo].[spAddAuthor]
#Author tyAuthor READONLY,
#new_identity INT = NULL OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- check if the author exists
IF NOT EXISTS (SELECT Id_Author FROM dbo.Authors
WHERE (dbo.Authors.Username = (SELECT Username FROM #Author)
OR dbo.Authors.phone = (SELECT phone FROM #Author)
OR dbo.Authors.email = (SELECT email FROM #Author)))
BEGIN
INSERT INTO dbo.Authors (Username, sexe, email, phone, address)
SELECT [Username], [sexe], [email], [phone], [address]
FROM #Author
-- output the new row
SELECT #new_identity = ##IDENTITY;
END
ELSE
BEGIN
-- get the author Id if already exists
SELECT #new_identity = (SELECT TOP 1 Id_Author
FROM dbo.Authors
WHERE (dbo.Authors.Username = (SELECT Username FROM #Author)
OR dbo.Authors.phone = (SELECT phone FROM #Author)
OR dbo.Authors.email = (SELECT email FROM #Author)))
END
END
I found that in the declaration of the parameters I put null beside the output and that what caused the problem.
#new_identity INT = NULL OUTPUT
but I don't understand why, I thought the 'null' was like the default value, or when you try to make the parameter optional you add null as default value.
can someone explain, please?

I need to write a Stored procedure to insert data in a column( unique constraint) in mysql

I need to write a Stored procedure to insert data in a column( unique constraint) in mysql and first i have to check if column is null then i have to check for duplicate, if not then insert the random generated data.
BEGIN
DECLARE key1 VARCHAR(10);
DECLARE accid varchar(32);
WHILE ( select count(*) from account where customerkey is null)>0 DO
SET key1 = (SELECT LEFT(MD5(UUID()), 7));
WHILE (SELECT count(*) FROM account WHERE customerkey = key1) < 1 DO
SET accid = (select id from account where customerkey is null limit 1);
update account set customerkey = key1 where id = accid;
END WHILE;
END WHILE;
END
One doesn't need a loop or separate variables in the procedure. Based on what you've posted, you should be able to get away with a single update statement:
update account set customerkey = LEFT(MD5(UUID()), 7) where customerkey is null;
Try that and let me know if it does the trick.
As an aside reassurance, yes, UUID() returns unique for each row.

SQL Stored Procedure IF ELSE logic error

I am trying to implement an insert stored procedure. Basically, how it works is the stored procedure will check whether the record exists or not, then proceed to perform insert. A variable #status will be used as indicator. However, I found out a particular problem with this query is that when I execute the query, it will return the result #status = 1 no matter the data existed or not. However, the INSERT function is fine without any problems just the #status. The following is my implementation:
CREATE PROCEDURE save_proc
(
#userid varchar(10)
#name varchar(30)
)
AS
DECLARE #status int
if exists ( SELECT * FROM table1 where userID = #userid AND userName = #name)
SET #status = 0
else
INSERT INTO table1 (userID, userName) VALUES (#userid, #name)
SET #status = 1
SELECT #status
Try to put it in BEGIN END
CREATE PROCEDURE save_proc
(
#userid varchar(10)
#name varchar(30)
)
AS
DECLARE #status int
if exists ( SELECT * FROM table1 where userID = #userid AND userName = #name)
SET #status = 0
else
begin
INSERT INTO table1 (userID, userName) VALUES (#userid, #name)
SET #status = 1
end
If you are not putting the BEGIN END then the scope of your else statement is just the next line which is getting executed ie, INSERT INTO table1 (userID, userName) VALUES (#userid, #name) is only under the scope of else block. And SET #status = 1 is outside the scope of else block. So once the else block executes the next query will be executed which is SET #status = 1
On a side note:
When you are checking for if exists then don't use the * wildcard. Instead you can use 1 i.e,
if exists ( SELECT 1 FROM table1 where userID = #userid AND userName = #name)
else
begin
INSERT INTO table1 (userID, userName) VALUES (#userid, #name)
SET #status = 1
end
Modify code as above. You haven't defined scope of your else. It was upto insert statement only. SET #status = 1 was executed everytime

SQL Server 2008 :: Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >=

I have [ReconcileUserInformationComputed] trigger on Userinformation table
Userinformation table has below rows
[ID] ,[CompanyID] ,[Status] ,[FirstName] ,[LastName]
UserinformationComputed table has below rows
[id] ,[CompanyID] ,[law_id] ,[Status] ,[FirstName] ,[LastName]
Below is my trigger
USE [einvoice]
GO
/****** Object: Trigger [dbo].[ReconcileUserInformationComputed] Script Date: 08/27/2014 10:53:08 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[ReconcileUserInformationComputed] ON [dbo].[UserInformation] AFTER INSERT,DELETE,UPDATE
AS
IF ##ROWCOUNT = 0 -- exit trigger when zero records affected
BEGIN
RETURN
END
SET NOCOUNT ON;
IF EXISTS (SELECT * FROM INSERTED)
BEGIN
IF EXISTS (SELECT * FROM DELETED)
BEGIN
--UPDATE
UPDATE [dbo].[UserInformationComputed]
SET -- use new values from inserted
CompanyID = (SELECT CompanyID from inserted),
law_id = (SELECT ID FROM inserted),
Status = (SELECT Status FROM inserted),
FirstName = (SELECT FirstName FROM inserted),
LastName = (SELECT LastName FROM inserted),
WHERE -- use original values from deleted
law_id = (SELECT ID FROM deleted)
END
ELSE
BEGIN
--INSERT
INSERT INTO [dbo].[UserInformationComputed] (CompanyID,law_id,Status,FirstName,LastName)
SELECT CompanyID,id,Status,FirstName,LastName) FROM inserted
END
END
ELSE IF EXISTS(SELECT * FROM DELETED)
BEGIN
--DELETE
DELETE FROM [dbo].[UserInformationComputed]
WHERE law_id = (SELECT id FROM deleted)
END
when try to update multiple users on Userinformation am getting
below error
Msg 512, Level 16, State 1, Procedure ReconcileUserInformationComputed, Line 16
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I made the changes as per the answer it worked for the above trigger but same changes didnt work for another trigger
ALTER TRIGGER [dbo].[ReconcileCrossRefComputed] ON [dbo].[CrossRef] AFTER INSERT, UPDATE, DELETE
AS
IF ##ROWCOUNT = 0 -- exit trigger when zero records affected
BEGIN
RETURN
END
SET NOCOUNT ON;
IF EXISTS (SELECT * FROM INSERTED)
BEGIN
IF EXISTS (SELECT * FROM DELETED)
BEGIN
--UPDATE
UPDATE [dbo].[CrossRefComputed]
SET -- use new values from inserted
SenderId = inserted.SenderId,
ReceiverId = inserted.ReceiverId,
ForeignRef = inserted.ForeignRef,
PolicyID = inserted.PolicyID,
From inserted
WHERE -- use original values from deleted
[CrossRefComputed].SenderId = inserted.SenderId
AND [CrossRefComputed].ReceiverId = inserted.ReceiverId
END
ELSE
BEGIN
--INSERT
INSERT INTO [dbo].[CrossRefComputed] (SenderId, ReceiverId, ForeignRef, PolicyID)
SELECT SenderId, ReceiverId, Effective, PolicyID FROM inserted
END
END
ELSE IF EXISTS(SELECT * FROM DELETED)
BEGIN
--DELETE
DELETE FROM [dbo].[CrossRefComputed]
WHERE SenderId in (SELECT SenderId FROM deleted)
AND ReceiverId in (SELECT ReceiverId FROM deleted)
END
COuld anyone please help me how to fix the procedure to handle updating multiple records?
Be aware that an UPDATE or a DELETE can affect more than one record and your trigger will have to deal with them at once, in this case your "inserted" or "deleted" table will have more than one record.
You will have to use a join in your DML. Something like this:
UPDATE [dbo].[UserInformationComputed]
SET
CompanyID = inserted.CompanyID,
law_id = inserted.ID,
Status = inserted.Status,
FirstName = inserted.FirstName,
LastName = inserted.LastName
from inserted
WHERE
UserInformationComputed.law_id = inserted.ID
I haven't perfectly understood your UPDATE logic, so you'll have to adapt my code.
In your DELETE command you may have just to change "=" to "in":
DELETE FROM [dbo].[UserInformationComputed]
WHERE law_id in (SELECT id FROM deleted)
Take a look at this:
http://msdn.microsoft.com/en-us/library/ms190752.aspx

How to loop the data and if username exists append incremental number e.g. JOHSMI1 or JOHSMI2

I have a userid table
UserId
JHOSMI
KALVIE
etc...
What I would like to do is create a select statement and pass user id, if the userid already exists then append 1 to the id, This gets complicated if you already have JHOSMI, JHOSMI1, then I want to return JHOSMI2.
Really appreciate help here.
Thanks in advance
edited 21-Jul
this is what i got so far.. but not working the way
select #p AS StaffID,
#old_p := #p,
#Cnt := #Cnt+1 As Lvl,
(SELECT #p :=Concat(#i, #Cnt)
FROM departmenttaff
WHERE upper(trim(UserId)) = upper(trim(StaffID))
AND upper(trim(department)) like upper(trim('SERVICE'))
) AS dummy
FROM (
SELECT
#i := upper(trim('JOHSMI')),
#p := upper(trim('JOHSMI')),
#old_p :='',
#Cnt:=0
) vars,
departmenttaff p
WHERE #p <> #old_p
order by Lvl Desc LIMIT 1;
This will do exactly what you want. You will need a unique constraint on your column.
You might also need to add in error code if success = 0.
This is in MSSQL, you will need to add the relevant commands for MySQL. I do not have MySQL so I cannot test it.
NOTE: You can replace the try catch with some IF EXISTS logic. I just prefer the try catch because its more stable for multiple threads.
begin tran
select * from #tmp
declare #success bit
declare #name varchar(50)
declare #newname varchar(50)
declare #nextid int
declare #attempts int
set #name = 'brad2something'
set #success = 0
set #attempts = 0
while #success = 0 and #attempts < 5 begin
begin try
set #attempts = #attempts + 1 -- failsafe
set #newname = #name
if exists (select * from #tmp where username = #name) begin
select #nextid = isnull(max(convert(int, substring(username, LEN(#name) + 1, 50))), 0) + 1
from #tmp where username like #name + '%' and isnumeric(substring(username, LEN(#name) + 1, 50)) = 1
set #newname = #name + CONVERT(varchar(20), #nextid)
end
insert into #tmp (username) values (#newname)
set #success = 1
end try begin catch end catch
end
--insert into #tmp (username)
--select
select #success
select * from #tmp
rollback
/*
drop table #tmp
create table #tmp (
username varchar(50) not null unique
)
insert into #tmp (username)
select 'brad'
union all select 'brad1'
union all select 'brad2something5'
union all select 'brad2'
union all select 'laney'
union all select 'laney500'
*/
I noticed you want to back fill data. If you want to back fill then this will work. It is extremely inefficient but there is no way around it. There is optimizing code you can put in for when an "error" occurs to prevent all previous counts from happening, but this will work.
begin tran
select * from #tmp
declare #success bit
declare #name varchar(50)
declare #newname varchar(50)
declare #nextid int
declare #attempts int
set #name = 'laney'
set #success = 0
set #attempts = 0
set #nextid = 1
while #success = 0 and #attempts < 5 begin
begin try
if exists (select * from #tmp where username = #name) begin
set #newname = #name + CONVERT(varchar(20), #nextid)
while exists (select * from #tmp where username = #newname) begin
set #nextid = #nextid + 1
set #newname = #name + CONVERT(varchar(20), #nextid)
end
end else
set #newname = #name
set #attempts = #attempts + 1 -- failsafe
insert into #tmp (username) values (#newname)
set #success = 1
end try begin catch end catch
end
--insert into #tmp (username)
--select
select #success
select * from #tmp
rollback
/*
drop table #tmp
create table #tmp (
username varchar(50) not null unique
)
insert into #tmp (username)
select 'brad'
union all select 'brad1'
union all select 'brad2something5'
union all select 'brad2'
union all select 'laney'
union all select 'laney500'
*/
Is it mandatory to have the count in same column? its better to have it in a different integer column. Anyways, if this is the requirement then select userid from table where userid like 'JHOSMI%', then do extract the number using mysql substr function.
For other people who might find this, here's a version in PostgreSQL:
create or replace function uniquify_username(varchar) returns varchar as $$
select $1 || coalesce((max(num) + 1)::varchar, '')
from
(select
substring(name, '^(.*?)[0-9]*$') as prefix,
coalesce(substring(name, '.*([0-9]+)$'), '0')::integer as num
from user1) users
where prefix = $1
$$ LANGUAGE sql;
I think it could be adapted to MySQL (though probably not as a stored procedure) but I don't have a MySQL server handy to do the conversion on.
Put a UNIQUE constraint on the column.
You didn't say what language you are using, so use this pseudo code
counter = 0
finished = false
while finished = false
{
try
{
if counter >= 1 then name = name + counter
counter = counter + 1
insert into table (name)
}
}
This code is extremely finicky. But will get the job done and there is no real other way to do this except for in sql, and you will always have some type of try catch to avoid two processes running at the same time. This way you use the unique key constraint to force the error, and supress it because it is expected.
I in no way condone using try/catch for business logic like this, but you are putting yourself in a situation thats unavoidable. I would say put the ID in a seperate column and make a unique constraint on both fields.
Proper solution:
Columns: Name, ID, Display Name
Unique constraint on: Name, ID
Display Name is a computed column (virtual) is Name + ID
If you do it this way, then all you have to do is INSERT INTO table (name, (select max() from table))