SQL Server : get the row Identity - sql

I have this as the first part of a stored procedure:
DECLARE #_id as int
if exists(select 1 from JTrack_Visitors WHERE cookie = #cookie)
begin
UPDATE JTrack_Visitors
SET LastSeen = #_now
WHERE cookie = #cookie
end
else
begin
INSERT INTO JTrack_Visitors(Cookie, CreatedOn, LastSeen)
VALUES (#cookie, #_now, #_now)
end
How can I set #_id to be the identity of the row either being inserted or updated? I need that value to use later in the stored procedure.
Thanks.

You can make use of OUTPUT clause in your both statements, in either case populate a table variable and later retrieve value from that table variable.
DECLARE #_id as int;
DECLARE #ID_Table TABLE(ID INT);
IF EXISTS (select 1 from JTrack_Visitors WHERE cookie = #cookie)
BEGIN
UPDATE JTrack_Visitors
SET LastSeen = #_now
OUTPUT inserted.ID INTO #ID_Table(ID)
WHERE cookie = #cookie
END
ELSE
BEGIN
INSERT INTO JTrack_Visitors(Cookie,CreatedOn,LastSeen)
OUTPUT inserted.ID INTO #ID_Table(ID)
VALUES (#cookie,#_now,#_now)
END
SELECT #_id = ID FROM #ID_Table;

Depending on the SQL Server version you're using you could use the MERGE statement including the OUTPUT clause to get the values you're looking for.
This would also eliminate the need to check for existence and either update or insert.

Related

Generate a unique column sequence value based on a query handling concurrency

I have a requirement to automatically generate a column's value based on another query's result. Because this column value must be unique, I need to take into consideration concurrent requests. This query needs to generate a unique value for a support ticket generator.
The template for the unique value is CustomerName-Month-Year-SupportTicketForThisMonthCount.
So the script should automatically generate:
AcmeCo-10-2019-1
AcmeCo-10-2019-2
AcmeCo-10-2019-3
and so on as support tickets are created. How can ensure that AcmeCo-10-2019-1 is not generated twice if two support tickets are created at the same time for AcmeCo?
insert into SupportTickets (name)
select concat_ws('-', #CustomerName, #Month, #Year, COUNT())
from SupportTickets
where customerName = #CustomerName
and CreatedDate between #MonthStart and #MonthEnd;
One possibility:
Create a counter table:
create table Counter (
Id int identify(1,1),
Name varchar(64)
Count1 int
)
Name is a unique identifier for the sequence, and in your case name would be CustomerName-Month-Year i.e. you would end up with a row in this table for every Customer/Year/Month combination.
Then write a stored procedure similar to the following to allocate a new sequence number:
create procedure [dbo].[Counter_Next]
(
#Name varchar(64)
, #Value int out -- Value to be used
)
as
begin
set nocount, xact_abort on;
declare #Temp int;
begin tran;
-- Ensure we have an exclusive lock before changing variables
select top 1 1 from dbo.Counter with (tablockx);
set #Value = null; -- if a value is passed in it stuffs us up, so null it
-- Attempt an update and assignment in a single statement
update dbo.[Counter] set
#Value = Count1 = Count1 + 1
where [Name] = #Name;
if ##rowcount = 0 begin
set #Value = 10001; -- Some starting value
-- Create a new record if none exists
insert into dbo.[Counter] ([Name], Count1)
select #Name, #Value;
end;
commit tran;
return 0;
end;
You could look into using a TIME type instead of COUNT() to create unique values. That way it is much less likely to have duplicates. Hope that helps

Using SCOPE_IDENTITY result in another query

I'm having difficulties with this code
EXECUTE Environment.dbo.psu_some_psu
#instanceId = 1
,#serverId = 2
,#ip = '111.111.111.111'
,#clientName = 'dev-contact'
,#applicationId = 9
,#accountId = 35
,#userID = 22
DECLARE #restoreId INT
SET #restoreId = SCOPE_IDENTITY()
UPDATE Environment.dbo.restore_requests
SET Status = 1
WHERE Id = #restoreId
The stored procedure insert a row with indentity and with a status by default.
After the execution of the stored procedure i use scope_identity to get the ID of the inserted row.
But i cant get the update to work, i think there is a problem with the WHERE condition.
Thanks
It's called SCOPE_IDENTITY() because it returns the last identity value inserted in the current scope - and since each procedure has it's own scope - you do not get the expected results.
Returns the last identity value inserted into an identity column in the same scope. A scope is a module: a stored procedure, trigger, function, or batch. Therefore, if two statements are in the same stored procedure, function, or batch, they are in the same scope.
Use an output parameter to return the scope_identity() from inside the procedure that actually executes the insert statement.
Also, please note that if you are inserting multiple records, the scope_identity() will return only the last value - in such cases you use the output clause on the insert statement to get a table containing all the newly inserted identity values.
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());

Microsoft SQL Server - default value provided by stored procedure

Is it possible to have a non-null column where the value is generated at insert by calling a stored procedure the parameters of which are values passed to insert into the row?
For example, I have table User:
| username | name | surname | id |
Insert looks like this:
INSERT INTO USER (username, name, surname)
VALUES ('myusername', 'myname', 'mysurname');
The id column is populated with an (integer) value retrieved by calling stored procedure mystoredproc with parameters myusername, myname, mysurname.
A further question is, would this stored procedure be called on each row, or can it be called in a grouped fashion. For example, I'd like my stored procedure to take the name and append a random integer to it so that that if I insert 100 users with the name 'David', they will get the same id and the stored procedure will be called only once. A bit of a bad example on the second point.
Good day,
Is it possible to have a non-null column where the value is generated at insert by calling a stored procedure
Option 1: please check if this work for you
Specify Default Value for the Column and use "NOT NULL"
create trigger on the table AFTER INSERT
Inside the trigger, you can use the virtual table "inserted" in order to get the inserted values.
Using these values (using the inserted table) you can update the column using the logic you need for all the rows at once
** there is no need to use external SP probably, but you can execute SP from trigger if needed
** All executed by a trigger is in the same transaction as the original query.
would this stored procedure be called on each row
NO! The trigger will be executed once for all rows you insert in the same statement. The inserted table includes all the rows which were inserted. In your update section (step 4) you can update all the rows which were inserted in once and no need to execute something for each row
** If you do use external SP which is executed from the trigger then you can pass it all the inserted table as one using Table-Valued Parameter
------------------- update ---------------
Here is a full example of using this logic:
drop table if exists T;
CREATE TABLE T (id int identity(2,2), c int NOT NULL default 1)
GO
CREATE TRIGGER tr ON T AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
UPDATE T SET T.c = T2.C + 1
FROM inserted T2
INNER JOIN T T1 ON T1.id = T2.id
END
INSERT T(c) values (1) -- I insert the value 1 but the trigger will change it to 1+1=2
select * from T
GO
-- test multiple rows:
INSERT T(c) values (10),(20),(30),(40)
select * from T
GO
DECLARE #rc INT = 0,
#UserID INT = ABS(CHECKSUM(NEWID())) % 1000000 + 1;
WHILE #rc = 0
BEGIN
IF NOT EXISTS (SELECT 1 FROM dbo.Users WHERE UserId= #UserId)
BEGIN
INSERT dbo.Users(UserId) WHERE Username = #UserName SELECT #UserId;
SET #rc = 1;
END
ELSE
BEGIN
SELECT #UserId = ABS(CHECKSUM(NEWID())) % 1000000 + 1,
#rc = 0;
END
END

Getting the value of a primary key from a merge

I have a stored proc that has a merge .. into statement. I am trying to get the value of the primary key out of the stored proc.
SET #NewCompanyID = #CompanyID
....
merge company as c
using ( select #CompanyID) as compAlt (company_id )
on compAlt.company_id = c.company_id
when matched
then
update set ....
when not matched
then
insert ... ;
SET #NewCompanyID = ##identity;
If the merge statement runs the update, i want to set #NewCompanyID to whatever value was passed into the stored proc (#CompanyID). If the insert statement was executed, then I want to pass the ##identity. How do I do this?
I added before merge into
DECLARE #SummaryOfChanges TABLE(Change VARCHAR(20));
then
OUTPUT $action INTO #SummaryOfChanges;
SELECT #Change = Change
FROM #SummaryOfChanges; /* there is always one update or insert */
IF #Change = 'INSERT'
BEGIN
....
END
This worked fine.

How to check if value is inserted successfully or not?

I have a procedure where I insert values into my table.
declare #fName varchar(50),#lName varchar(50),#check tinyint
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
EDITED:
Now I want check if it inserted successfully set #check = 0 else #check = 1
You can use ##ROWCOUNT server variable immediately after the insert query to check number of affected rows by the insert operation.
declare #fName varchar(50) = 'Abcd',
#lName varchar(50) = 'Efgh'
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
PRINT ##ROWCOUNT --> 0- means no rows affected/nothing inserted
--> 1- means your row has been inserted successfully
For your requirement, you could use a Case statement(as per comment):
--If you need #check as a bit type please change Int to bit
DECLARE #check Int = CASE WHEN ##ROWCOUNT = 0 THEN 1 ELSE 0 END
You need to use ##ROWCOUNT
It returns the number of rows affected by the last statement. If the number of rows is more than 2 billion, use ROWCOUNT_BIG.
##ROWCOUNT is both scope and connection safe.
In fact, it reads only the last statement row count for that
connection and scope.
It’s safe to use ##ROWCOUNT in SQL Server even when there is a trigger
on the base table. The trigger will not skew your results; you’ll get
what you expect. ##ROWCOUNT works correctly even when NOCOUNT is set.
so you query should be:
declare #fName varchar(50), #lName varchar(50), #check tinyint = 0
...
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
if ##ROWCOUNT>0
set #check = 1
You can use ##rowcount after insert table, like this:
DECLARE #check int
INSERT INTO Employees (Name,Email,Phone,[Address])
VALUES('Test','test#mail.com','','')
if(##ROWCOUNT>0)
SET #check=1
SELECT #check;
In SQL-Sever you can use OUTPUT clause to check if values are inserted successfully.
By following query
declare #fName varchar(50),#lName varchar(50)
INSERT INTO myTbl(fName,lName) OUTPUT inserted.* values(#fName,#lName) ;
IF the values are inserted it will show output of inserted values. You can also store these values into new table.
I can say you can simply check the previous numbers of rows before inserting the new.
for example previous you have 50 rows. just store the numbers of rows in a variable and check after the insert query if numbers of row increased or no.
yes little extra chunk of code but easy to understand