I have a .NET 2010 app hitting a SQL2000 db. The code is pretty basic. When I insert a record, the record is inserted, but the id is not returned. The id column is an int and it is an Idetity. Here is the stored proc...
ALTER PROCEDURE Insert_Vendor
#CorpID as varchar(255),
#TaxpayerID as varchar(255)
AS
Insert into dbo.Vendor
(
vdr_CorpID,
vdr_TaxpayerID
)
values
(
#CorpID,
#TaxpayerID
)
IF ##error <> 0
BEGIN
RETURN -1
END
ELSE
RETURN ##Identity
GO
And on the receiving end...
int myID = (int)(db.ExecuteScalar(dbCommand));
You should always use SCOPE_IDENTITY()
NULL can't be returned via RETURN from a stored proc. You'd get a SQL warning and it would return zero.
ExecuteScalar looks for the 1st row, 1st column of a recordset. There is no recordset above
... So you'd use SELECT SCOPE_IDENTITY() not RETURN SELECT SCOPE_IDENTITY()
ExecuteScalar
executes the query, and returns the
first column of the first row in the
result set returned by the query
So you need to re-write the RETURN statements as
SELECT -1
and (since scope_indentity() returns numeric(38,0))
SELECT CAST(SCOPE_IDENTITY() AS INT)
respectively
you have to call ##IDENTITY right after the insert, use Scope_identity() instead.
Because your question leaves out a lot of details I will just mention a few possible ways around this as it seems impossible to answer a question without all the details. But it's your first time here so you'll get better. You will right?
Anyways first I would say you should always use scope_identity as it is safer. There could be things going on behind the scenes with triggers that could cause this real problems. Stick with scope_identity and you shouldn't have to worry.
Second I would suggest instead of
RETURN
use
SELECT SCOPE_IDENTITY()
Lastly I would say why not just use an OUTPUT parameter vs returning a result. I don't have anything to support this next statement but I would think it is better. Again no proof on that but it just seems like less overhead with output parameter vs resultset that comes with schema.
Just my thoughts.
I personally would recommend using SCOPE_IDENTITY instead of ##IDENTITY. That being said the problem is in the stored procedure. Devio above was correct the execute scalar is looking for the first column of the first row. The RETURN statement will not do this so you will need to use either one of the below items:
IF ##error <> 0
BEGIN
Select -1
END
ELSE
Select ##Identity
or:
IF ##error <> 0
BEGIN
Select -1
END
ELSE
Select SCOPE_IDENTITY()
Related
I'm using SQL Server 2008.
I have an interesting scenario where a stored procedure (written by a "power user") has an okay runtime of (around 4 seconds) if there's data in the primary table. If the search value doesn't exist, the run time averages out at about 3 minutes. Because of how the process works, and the web application that uses the procedure, it requires an empty result set in the case of no data.
I've tested the logic below with values that have data and values that don't and the flow seems to work; however, when I put my actual query in the else statement, it seems like that part is always being evaluated despite my knowing that logic branch shouldn't execute.
DECLARE #spId int
SELECT #spId = td.mainId
FROM dbo.PRIMARYTABLE
WHERE td.longId = #searchVal
IF #spId < 1 OR #spId IS NULL
BEGIN
select 'RETURN EMPTY RESULT SET' as test
END
ELSE
BEGIN
SELECT 'DO ACTUAL QUERY' as test
END
When I test this with a dummy value, such as 1111, the select 'RETURN EMPTY RESULT SET' as test is returned. When I use a value that I know exists, the SELECT 'DO ACTUAL QUERY' as test is returned. When I replace "SELECT 'DO ACTUAL QUERY' as test" with the actual heavy duty query and use the same non-existent dummy value, it still looks like the ELSE clause is reached.
What am I missing here?
Perhaps you are not showing everything. There is an counter-intuitive thing about assignment in select where no rows are returned - the value of variable will not be cleared. Paste this in SSMS:
declare #searchVal as int
set #searchVal=111
DECLARE #spId int
set #spId = 2134
SELECT #spId = td.mainId
FROM (select 839 as mainId, 0 as longid) td
where td.longId = #searchVal
print #spid
#spid will be 2134. This is why you should always test using ##rowcount, in you case
IF ##rowcount = 0 or #spId < 1 or #spId is null
BEGIN
select 'RETURN EMPTY RESULT SET' as test
END
ELSE
BEGIN
SELECT 'DO ACTUAL QUERY' as test
END
There is also a possibility of duplicated data by longId, returning random mainid from rows that satisfy #searchval condition.
Other than that, I would not know.
Thank you all for your suggestions. I apologize for the lack of posting the entire stored procedure, but I'm not allowed to share that exact code. The snippet I began with was psuedo code (well, real code with tables and fields renamed).
I think Nikola Markovinović may be onto something with his answer and article link. This entire ordeal has been sort of maddening. I googled, debugged, and did the whole thing again, then search on stack overflow. After a few changes from your suggestions, the procedure magically started responding with the run time I thought it should. I don't think some of the initial changes took or maybe they weren't be cached by sql server correctly; I've got nothing but guesses.
It's very strange because, for a good hour or more, it was running as if it had never been changed (performance wise)...then it just kicked into gear. I wonder if this isn't my fault and maybe I didn't alter the one on staging like I did the one on Test...that seems the most feasible explanation.
Anyhow, thank you for your suggestions. I've learned a few things so that's always good.
Working with Sql Server. Writing a stored procedure. Here is the pseudocode for what I want to achieve:
IF EXISTS ( SELECT field1
FROM t1
WHERE field1 = ... AND field2 = ...)
BEGIN
SELECT field1
FROM t1
WHERE field1 = ... AND field2 = ...
END
any better way of doing this? Any help appreciated.
Chirayu
Update: The problem is that the same query is executed twice. I cannot also just the run query once and return null (if the result is null i would like to return an alternative result).
I have done this before using a CTE and table variable, it requires more lines of code but the query is only written once, therefore your logic exists in a single place.
DECLARE #Results TABLE (Result INT);
WITH ResultsCTE AS
(
--Your query goes here
SELECT 1 as Result
WHERE 1 = 1
)
INSERT INTO #Results
SELECT Result
FROM ResultsCTE
IF (SELECT COUNT(*) FROM #Results) > 0
BEGIN
SELECT * FROM #Results
END
ELSE BEGIN
SELECT 'Do Something Else or Do Nothing!'
END
You could check ##ROWCOUNT after running the query once to determine whether or not to return the value:
http://msdn.microsoft.com/en-us/library/ms187316.aspx
If the select doesn't yield any results, no results will be returned. I don't see any reason to use a condition here, unless I'm missing something...
A stored procedure that sometimes returns a result while sometimes it doesn't would be a nightmare to use from any API. The client side API has different entry points depending on whether you return a result set (SqlCommand.ExecuteReader) or it does not return a result set (SqlCommand.ExecuteNonQuery). It would be impossible for the application to know ahead of time which API to use! Modeling tools use the SET FMTONLY option to analyze the metadata of returned result sets and the modeling tools are very confused when your returned result set start changing shape at random. In other words, you are down the wrong path, stop and turn around.
Just run the query, it no rows match your criteria it will simply return an empty result set. Which is exactly what every client API and modeling tool expects from your procedure.
I have an insert sql with sql server and then call select ##identity straight after, i am trying to use the identity id with a stored procedure, is this even possible
eg
insert into ...
select ##identity
EXEc add 'ss' ##identity
thanks
a
edit---
i basically want to use the value of the id which i'm getting now with
SELECT SCOPE_IDENTITY() ;
to use in a query straight after the insert.
Yes, this is possible, though you are probably better off using SCOPE_IDENTITY().
See this SO question about the best way to get the identity of an inserted row.
Answering the (now deleted) question in the comments...
It is possible to use ##IDENTITY directly in the parameter list of the stored procedure call. For SCOPE_IDENTITY() (which you should be using to avoid problems if a trigger is later added to the table) this syntax is not allowed you need to use an intermediate variable as below.
declare #id int
insert into ...
set #id = SCOPE_IDENTITY()
EXEC AddEmp2 0,#id
i have written stored procedure, where i am first checking whether the record for a particular is repeated more than thrice, if yes it should not insert new record, else it should insert the record in the database.
now in the if condition i have insert query, now i want to know how should i be able to know whether the insert query fired successfully or not. because if the IF statement fails it wont execute insert query, and what should i write in the else statement, so that i can come to know whether the insert query executed or not.
i am using VB.Net as a front end... please tell me the condition how i will be able to know the insert query fired or not.
Regards
Abbas Elecrticwala
you can return a bit value depending on the insert. like
create proc myproc
-- your variables here
as
begin
if (your condition )
begin
your insert query
select '1'
end
else
begin
select '0'
end
end
you can check the result in vb, and you will know depending on the bit value.
How can I get back the autogenerated ID for a new record I just inserted?
(Using ASP classic and MSSQL 2005)
SELECT SCOPE_IDENTITY()
using ##IDENTITY can have unexpected results, so be careful how you use that one. Triggers that insert records to other tables will cause the ##IDENTITY value to change - where SCOPE_IDENTITY() will give you the last identity from only your current scope.
Here's a sample that'll show the difference between ##IDENTITY and SCOPE_INSERT() and how they can return different values..
use tempdb
go
create table table1
(ID int identity)
go
create table table2
(ID int identity(100, 1))
go
create trigger temptrig
on table1
for insert
as
begin
insert table2
default values;
end
go
insert table1
default values;
select SCOPE_IDENTITY(),
##IDENTITY
Another option that nobody has discussed here is to use the OUTPUT clause that is in SQL 2005. In this case, you'd just have to add the output clause to your insert, and then catch that recordset from your code. This works well when inserting multiple records instead of just 1...
use tempdb
go
create table table1
(ID int identity)
go
insert table1
output inserted.ID
default values;
--OR...
insert table1
output inserted.$identity
default values;
SELECT ##IDENTITY usually works, but could return the identity of a record inserted because of a trigger or something, and not the original.
SELECT SCOPE_IDENTITY is what I'd recommend. It returns values inserted only within the current scope.
There is also a "IDENT_CURRENT(tablename)" that returns the last identity inserted for a specific table.
There are three ways to get the the last identity in sql.
They were already mentioned by others, but for completeness:
##IDENTITY - can also return ids created in other objects in the same scope (think triggers)
IDENT_CURRENT - limited to a table, but not to your scope, so it can give bad results for busy tables
Scope_Idenity() - Limited to the scope of the request. Use this 99% of the time
Additionally, there are three ways to take that ID and return it to your client code:
Use an output parameter in a stored procedure
INSERT INTO [MyTable] ([col1],[col2],[col3]) VALUES (1,2,3);
SELECT #OutputParameterName = Scope_Identity();
Use a return value.
INSERT INTO [MyTable] ([col1],[col2],[col3]) VALUES (1,2,3);
Return Scope_Identity();
Select the id into a result set. For example, your sql statement would look something like this:
Dim ResultID As Integer
Dim strSQL As String
strSQL = "INSERT INTO [MyTable] ([col1],[col2],[col3]) VALUES (1,2,3); SELECT Scope_Identity();"
rsResults.Open strSQL, oConn
ResultID = rsResults("ID")
Unfortunately (or fortunately, from my point of view) my Classic ASP it too far gone to show examples of the first two from client code.
SELECT ##Identity or SELECT SCOPE_IDENTITY() both work however Selecting SCOPE_Identity() is safer because it returns the last auto generated ID within your current scope. So for example assume we have a table called ScopeIDTable and on this table we have a trigger. This trigger will insert into a record into TriggerIdTable both tables have an auto increment column.
If you use SELECT ##Identity you will get the last auto increment in that session which would be the Id generated from within the trigger (TriggerIdTable).
If you use SELECT SCOPE_IDENTITY() you will get the id from your ScopeIdTable.
You run the query
select scope_identity()
using the same database connection, before doing anything else with it. The result is, as you probably expect, a record set containing a single row that has a single field. You can access the field using index 0, or you can give it a name if you prefer that:
select scope_identity() as lastId
I always wondered why one would ever want to use
##identity
since
select scope_identity()
obviously is the most save way to accomplish what Scot is asking for.
Where multiple records need to inserted at once in a set-based fashion, it can get more interesting.
I have sometimes used GUIDs generated clientside (but for classic ASP you'd probably need to use a utility to generate the values) or, more often, a NEWSQUENTIALID() constraint on the GUID key column at the server end.
I'm aware not everyone like GIUDS though, for some quite valid reasons (their size and how it affects indexing/paging for one).
http://www.sqlmag.com/Articles/Index.cfm?ArticleID=50164&pg=2
Thanks all who suggested SELECT SCOPE_IDENTITY(). I was able to create a stored procedure:
USE [dbname]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[spInsert]
(
#Nn varchar(30)
)
AS
BEGIN TRANSACTION InsertRecord
INSERT INTO A (Nn)
VALUES (#Nn)
SELECT NewID = SCOPE_IDENTITY() -- returns the new record ID of this transaction
COMMIT TRANSACTION InsertRecord
and call the sproc using VB:
Dim strNn '<- var to be passed'
Set cn = Server.CreateObject("ADODB.Connection")
connectString = "DSN"
cn.Open connectString, "user", "PW0rd"
Set rs = Server.CreateObject("ADODB.Recordset")
set rs = cn.Execute("EXEC [dbname].[dbo].[A] #Nn=" & strNn)
'return the value'
resultID = rs(0)
I can now use resultID anytime I refer to the newly created ID.