How to check if value is inserted successfully or not? - sql

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

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

Stored procedure with AS MERGE not returning anything?

EDIT: Sequential invoice numbering is the law in multiple countries.
EDIT: Poor variable naming on my part suggested I wanted to use my generated Id as a key. This is not the case. Should have stuck with 'invoiceNumber'.
I have the exact same question as posed here https://stackoverflow.com/a/24196374/1980516
However, since the proposed solution threw a syntax error, I've adapted it to use a cursor.
First, there is the stored procedure that generates a new Nr, for a given Business+Year combination:
CREATE PROCEDURE PROC_NextInvoiceNumber #businessId INT, #year INT, #Nr NVARCHAR(MAX) OUTPUT
AS MERGE INTO InvoiceNextNumbers ini
USING (VALUES (#businessId, #year)) Incoming(BusinessId, Year)
ON Incoming.BusinessId = ini.BusinessId AND Incoming.Year = ini.Year
WHEN MATCHED THEN UPDATE SET ini.Nr = ini.Nr + 1
WHEN NOT MATCHED BY TARGET THEN INSERT (BusinessId, Year, Nr)
VALUES(#businessId, #year, 1)
OUTPUT INSERTED.Nr;
Then, using that stored procedure, I've created an INSTEAD OF INSERT trigger:
CREATE TRIGGER TRIG_GenerateInvoiceNumber ON Invoices INSTEAD OF INSERT
AS
BEGIN
DECLARE #BusinessId INT
DECLARE #InvoiceId INT
DECLARE #BillingDate DATETIME2(7)
-- Cursors are expensive, but I don't see any other way to call the stored procedure per row
-- Mitigating factor: Mostly, we're only inserting one Invoice at a time
DECLARE InsertCursor CURSOR FAST_FORWARD FOR
SELECT BusinessId, Id, BillingDate FROM INSERTED
OPEN InsertCursor
FETCH NEXT FROM InsertCursor
INTO #BusinessId, #InvoiceId, #BillingDate
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #year INT
SET #year = year(#BillingDate)
DECLARE #Number NVARCHAR(MAX)
EXEC PROC_NextInvoiceNumber #BusinessId, #year, #Number OUTPUT
-- SET #Number = 'this works'
INSERT INTO Invoices (BusinessId, Id, BillingDate, Number)
VALUES (#BusinessId, #InvoiceId, #BillingDate, #Number)
FETCH NEXT FROM InsertCursor
INTO #BusinessId, #InvoiceId, #BillingDate
END
CLOSE InsertCursor
DEALLOCATE InsertCursor
END
If I uncomment SET #Number = 'this works', then in my database that exact string ('this works') is successfully set in Invoice.Number.
Somehow, my OUTPUT parameter is not set and I can't figure out why not.. Can someone shed a light on this?
EDIT update in response to comments (thank you):
I have a composite key (BusinessId, Id) for Invoice. The desired end result is a unique Invoice Identifier Number of the form '20180001' that is a continuous sequence of numbers within the businessId. So business 1 has invoice Numbers 20180001, 20180002, 20180003 and business 2 also has invoice numbers 20180001, 20180002, 20180003. (But different composite primary keys)
I don't want that cursor either, but I saw no other way within the framework as suggested by the question I refer to up above.
Manual call of PROC_NextInvoiceNumber with existing business id and year returns NULL.
If I try to set Id in PROC_NextInvoiceNumber, I get A MERGE statement must be terminated by a semi-colon (;). if I set it inside the MERGE or The multi-part identifier "INSERTED.Nr" could not be bound. if I set outside the MERGE.
Your OUTPUT parameter is never set. You are using the OUTPUT clause of the MERGE statement to create a result set. This is unrelated to assigning a value to a parameter.
MERGE INTO..
USING ... ON ...
WHEN MATCHED THEN UPDATE ...
WHEN NOT MATCHED BY TARGET THEN INSERT ...
OUTPUT INSERTED.Nr; /* <-- HERE this is the OUTPUT *clause* */
Change the code to actually assign something to #Nr:
SET #Nr = ...
The typical way is to use the OUTPUT clause to store the desired value into a table variable and then assign the value to the desired output *variable:
DECLARE #t TABLE (Nr NVARCHAR(MAX));
MERGE INTO..
USING ... ON ...
WHEN MATCHED THEN UPDATE ...
WHEN NOT MATCHED BY TARGET THEN INSERT ...
OUTPUT INSERTED.Nr INTO #t;
SELECT #Nr = Nr FROM #t;

SQL Server : get the row Identity

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.

SQL insert statement from another table loop

I am trying to take values from a table and insert them into another table. However, there is one database column that needs to increase by 1 value each time. This value though is not an identity insert column, the value comes from another table. There is another db column that acts as a counter. I wrote a couple of things but it just isnt helping:
(121 documents)
declare #count int;
set #count=0
while #count<=121
begin
insert into cabinet..DOCUMENT_NAMES_ROBBY (TAG,TAGORDER,ACTIVE,QUEUE,REASON,FORM,DIRECT_VIEW,GLOBAL,FU_SIGN,
SIGN_X,SIGN_Y,SIGN_W,SIGN_H,ANNOTATE,doctype_id,CODE,DOC_TYPE,SET_ID,SUSPEND_DELAY,Text_Editing,Restrict_Viewing,Viewing_Expire,
Viewing_Period,DocHdrLength,DocFtrLength,DocRuleId,Outbound,SigQueue,SigReason) select TAG,TAGORDER,ACTIVE,QUEUE,REASON,FORM,
DIRECT_VIEW,GLOBAL,FU_SIGN,
SIGN_X,SIGN_Y,SIGN_W,SIGN_H,ANNOTATE,(select nextid from cabinet..Wfe_NextValue where Name='documents')+1, CODE,DOC_TYPE,'2',SUSPEND_DELAY,Text_Editing,Restrict_Viewing,Viewing_Expire,
Viewing_Period,DocHdrLength,DocFtrLength,DocRuleId,Outbound,SigQueue,SigReason from cabinet..document_names where SET_ID ='1'
update cabinet..Wfe_NextValue set NextID=NextID+1 where Name='documents'
set #count=#count+1
end
That database column is the doctype_id. Above obviously comes out wrong and puts like 14,000 rows in the table. Basically I want to take every single entry from document_names and put it in document_names_robby...except the doctype_id column should take the value from wfe_nextvalue +1, while at the same time increasing that number in that table by 1 BEFORE inserting the next document name into document_Names_Robby. Any help is appreciated
Many popular databases support sequences. For the sequence, there is a function nextval that returns the sequence value and increments the sequence counter and currval that returns the latest previously returned value, also you can set the initial value and increment. Sequences are thread safe when storing counters in the table column is not.
Rewrite your code using sequences.
Assuming that you are using SQL Server database. Use IDENTITY function
SELECT *, IDENTITY(int, 1,1) AS IDCol FROM Cabinet.DocumentNames INTO #Tab1 WHERE Set_Id = '1';
insert into cabinet..DOCUMENT_NAMES_ROBBY (TAG,TAGORDER,ACTIVE,QUEUE,REASON,FORM,DIRECT_VIEW,GLOBAL,FU_SIGN,
SIGN_X,SIGN_Y,SIGN_W,SIGN_H,ANNOTATE,doctype_id,CODE,DOC_TYPE,SET_ID,SUSPEND_DELAY,Text_Editing,Restrict_Viewing,Viewing_Expire,
Viewing_Period,DocHdrLength,DocFtrLength,DocRuleId,Outbound,SigQueue,SigReason)
SELECT * FROM #Tab1;
DROP TABLE #Tab1;
declare #count int;
set #count=0
declare #nextId int;
select #nextId= nextid from cabinet..Wfe_NextValue where Name='documents'
while #count<=121
begin
insert into cabinet..DOCUMENT_NAMES_ROBBY (TAG,TAGORDER,ACTIVE,QUEUE,REASON,FORM,DIRECT_VIEW,GLOBAL,FU_SIGN,
SIGN_X,SIGN_Y,SIGN_W,SIGN_H,ANNOTATE,doctype_id,CODE,DOC_TYPE,SET_ID,SUSPEND_DELAY,Text_Edi ting,Restrict_Viewing,Viewing_Expire,
Viewing_Period,DocHdrLength,DocFtrLength,DocRuleId,Outbound,SigQueue,SigReason) select TAG,TAGORDER,ACTIVE,QUEUE,REASON,FORM,
DIRECT_VIEW,GLOBAL,FU_SIGN,
SIGN_X,SIGN_Y,SIGN_W,SIGN_H,ANNOTATE,(select nextid from cabinet..Wfe_NextValue where Name='documents')+1, CODE,DOC_TYPE,'2',SUSPEND_DELAY,Text_Editing,Restrict_Viewing,Viewing_Expire,
Viewing_Period,DocHdrLength,DocFtrLength,DocRuleId,Outbound,SigQueue,SigReason from cabinet..document_names where SET_ID ='1'
set #count=#count+1
end
update cabinet..Wfe_NextValue set NextID=NextID+121 where Name='documents'

sql stored procedure not working(no rows affected)

trying to get this stored procedure to work.
ALTER PROCEDURE [team1].[add_testimonial]
-- Add the parameters for the stored procedure here
#currentTestimonialDate char(10),#currentTestimonialContent varchar(512),#currentTestimonialOriginator varchar(20)
AS
BEGIN
DECLARE
#keyValue int
SET NOCOUNT ON;
--Get the Highest Key Value
SELECT #keyValue=max(TestimonialKey)
FROM Testimonial
--Update the Key by 1
SET #keyValue=#keyValue+1
--Store into table
INSERT INTO Testimonial VALUES (#keyValue, #currentTestimonialDate, #currentTestimonialContent, #currentTestimonialOriginator)
END
yet it just returns
Running [team1].[add_testimonial] ( #currentTestimonialDate = 11/11/10, #currentTestimonialContent = this is a test, #currentTestimonialOriginator = theman ).
No rows affected.
(0 row(s) returned)
#RETURN_VALUE = 0
Finished running [team1].[add_testimonial].
and nothing is added to the database, what might be the problem?
There may have problems in two place:
a. There is no data in the table so, max(TestimonialKey) returns null, below is the appropriate way to handle it.
--Get the Highest Key Value
SELECT #keyValue= ISNULL(MAX(TestimonialKey), 0)
FROM Testimonial
--Update the Key by 1
SET #keyValue=#keyValue+1
b. Check your data type of the column currentTestimonialDate whether it is char or DateTime type, if this field is datetime type in the table then convert #currentTestimonialDate to DateTime before inserting to the table.
Also, check number of columns that are not null allowed and you're passing data to them.
If you're not passing data for all columns then try by specifying columns name as below:
--Store into table
INSERT INTO Testimonial(keyValue, currentTestimonialDate,
currentTestimonialContent, currentTestimonialOriginator)
VALUES (#keyValue, #currentTestimonialDate,
#currentTestimonialContent, #currentTestimonialOriginator)
EDIT:
After getting the comment from marc_s:
Make keyValue as INT IDENTITY, If multiple user call it concurrently that wont be problem, DBMS will handle it, so the ultimate query in procedure might be as below:
ALTER PROCEDURE [team1].[add_testimonial]
-- Add the parameters for the stored procedure here
#currentTestimonialDate char(10),
#currentTestimonialContent varchar(512),#currentTestimonialOriginator varchar(20)
AS
BEGIN
SET NOCOUNT ON;
--Store into table
INSERT INTO Testimonial VALUES (#currentTestimonialDate,
#currentTestimonialContent, #currentTestimonialOriginator)
END
Two issues that I can spot:
SELECT #keyValue=max(TestimonialKey)
should be
SELECT #keyValue=ISNULL(max(TestimonialKey), 0)
To account for the case when there are no records in the database
Second, I believe that with NOCOUNT ON, you will not return the count of inserted rows to the caller. So, before your INSERT statement, add
SET NOCOUNT OFF