VB.Net Get scope_identity() to be used in a transaction - sql

I am using a VB.Net transaction to execute two queries. There are two tables & following is an example structure.
USER
----
1. USER_ID - int (PK) AUTO_INCREMENT
2. USER_NAME - varchar(20)
ADDRESS
-----
1. USER_ID
2. USER_ADDRESS
As this basic structure represents a USER can have many photos. Whenever I insert a new record to the USER table photos of the user should be saved with the USER_ID which was automatically created.
I know that I need to use SCOPE_IDENTITY() for this purpose but I always get NULL for the SCOPE_IDENTITY() value, this isn't because of a trigger or anything else. Issue lies on how VB.Net creates my INSERT statement.
Here is how the queries look like in the SQLServer Profiler
Insert to the USER Table
exec sp_executesql N'INSERT INTO USER(USER_NAME) VALUES (#USER_NAME)',N'#USER_NAME nvarchar(4)',#USER_NAME =N'ABCD'
Insert to the ADDRESS Table
exec sp_executesql N'INSERT INTO ADDRESS(USER_ID,USER_ADDRESS) VALUES ((SELECT SCOPE_IDENTITY()),#USER_ADDRESS)',N'# USER_ADDRESS nvarchar(10)',#USER_ADDRESS=N'ABCDEFGHIJ'
I have appended the SELECT SCOPE_IDENTITY() directly in to the second query & I think SQL thinks that the command SCOPE_IDENTITY() is a string, how do I prevent this from happening.

As the name implies, scope_identity() is local to a scope. And sp_executesql runs inside its own scope. A later call to sp_executesql has no memories of the earlier scope.
The most logical solution would be to run both queries in the same scope. I'm not sure why you are using sp_executesql; perhaps you can omit that. Most clients run something like:
INSERT INTO USER(USER_NAME) VALUES (#USER_NAME);
SELECT SCOPE_IDENTITY() AS ID;
The client program can get the ID from the resulting row set. It can then pass the ID as a parameter to the INSERT queries for addresses. No sp_executesql required.
If you must use more than one sp_executesql, consider using anoutput parameter to ferry the identity out:
declare #ID bigint
exec sp_executesql
N'INSERT INTO USER(USER_NAME) VALUES (#USER_NAME);
SELECT #ID = SCOPE_IDENTITY();',
N'#USER_NAME nvarchar(4), #ID bigint output',
#USER_NAME = N'ABCD',
#ID = #ID output;
You can now pass #ID as a variable to your second sp_executesql.

Related

Set a local variable and use throughout the query batch?

Once I insert default values in the table I store the result of scope identity in a variable
insert into OrderPlaced default values;
declare #id bigint;
set #id = SCOPE_IDENTITY();
After this, I have to run some other pieces of code that change the value of scope identity and after running those pieces of code I have to use the value of #id again but it shows an error saying that I must declare the variable which I have already done above.
EXEC dbo.GetRecieptById #ID = #id;
Unfortunately, I can't just select the whole code block and execute it at once as this is for a presentation and I have to show each individual steps.
Your request is how to persist the variable across batches - not within a batch.
One way would be to use SESSION_CONTEXT
declare #id bigint;
insert into OrderPlaced default values;
set #id = SCOPE_IDENTITY();
EXEC sys.sp_set_session_context #key= N'#id',#value = #id
GO
declare #id bigint = CAST(SESSION_CONTEXT(N'#id') AS BIGINT)
EXEC dbo.GetRecieptById #ID = #id;
What you wanna do ?
If you want to access the latest data in your Order Table, you can access the latest data with this code.
SELECT MAX(ID) FROM OrderPlaced
The local variable can not be used in a separate execution. You have to store all values in a temporary table.
These tables are stored in tempdb. Use local temporary table with one # or global temporary table with two ## at the beginning of the table name as follow:
create table #local_temp_table
(Id bigint not null);
...
insert into #local_temp_table ...
...
select Id from #local_temp_table;
OR
create table ##global_temp_table
(Id bigint not null);
...
insert into ##global_temp_table ...
...
select Id from ##global_temp_table;
They are automatically dropped when they go out of scope, however, you can drop them manually.
Take a look at the following link:
Temporary Tables in SQL Server

Creating Stored Proc to Insert into a View

I have created some new tables that I need to insert to on a semi-regular basis. Due to normalization I decided to build a view on top of the base tables to make reports more logical for myself and EU's. I got the bright idea to try to use a stored procedure to push inserts into the base tables via a different view. I can run the insert statement in SSMS successfully, but when I try to create it into a stored procedure it will run because it appears to think my insert is a function.
Here is the error:
Msg 215, Level 16, State 1, Procedure jedi.p_ForcePush, Line 12
Parameters supplied for object 'jedi.v_midichlorians' which is not a function. If the parameters are intended as a table hint, a WITH keyword is required.
Here is my script:
CREATE PROCEDURE jedi.p_ForcePush
#Field varchar(25) = NULL,
#Value varchar(250) = NULL
AS
BEGIN
SET NOCOUNT ON;
insert jedi.v_midichlorians (#field) values (#value)
END
GO
I have poured out my petition to the googles but haven't found a good solution. I have tried lots of different combo's in my syntax but nothing doing.
Any help is much appreciated! (ps-SQL 2012)
CREATE PROCEDURE jedi.p_ForcePush
#Field varchar(25) = NULL,
#Value varchar(250) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'INSERT INTO jedi.v_midichlorians (' + QUOTENAME(#field)
+ N') values (#value)'
EXECUTE sp_executesql #sql
, N'#Value varchar(250)'
, #Value
END
GO
When you pass #field param as a parameter sql server treats it as a string not an object name, Using QUOTENAME() wraps the passed column name in [] square brackets telling sql server explicitly that it is an object(Table, column) name.
On a side note If your View has only one underlying table on then use view to insert values other wise use the table name.
If you do have more then one underlying table in your View's definition and you want to insert data using view then you will need to create Instead of triggers.
Best option is to do all insert, update delete operation directly to tables, avoid using views and then triggers for view based on more then one underlying tables.
though you mentioned that insert stmts run fine in ssMS, can you confirm that the same insert stmt you ran in SSMS ? becaus ethere is an error in this stmt.
"insert jedi.v_midichlorians (#field)"
syntex is inccoect and column name should dnot have "#" right?
also is this view based on a single table ?

Iterate through XML variable in SQL Server

I have a XML variable in a stored procedure (SQL Server 2008), its sample value is
<parent_node>
<category>Low</category>
<category>Medium</category>
<category>High</category>
</parent_node>
I have to take each category and insert into table as a separate record. How to iterate in XML and take individual node value?
If I want to call a stored procedure and send each category as input parameter, how we can do that? The stored procedure is legacy one, which accept only one category at at time. I am trying to do invoke procedure in this way.
loop fetch single category from xml variable.
invoke stored procedure with current category.
move to next category.
loop until list contain value.
Any help will be appreciated.
Something like this?
DECLARE #XmlVariable XML = '<parent_node>
<category>Low</category>
<category>Medium</category>
<category>High</category>
</parent_node>'
INSERT INTO dbo.YourTargetTable(CategoryColumn)
SELECT
XTbl.Cats.value('.', 'varchar(50)')
FROM
#XmlVariable.nodes('/parent_node/category') AS XTbl(Cats)
Update: if you must use the old legacy stored procedure and cannot change it (that would be my preferred way of doing this), then you would have to do the row-by-agonizing-row (RBAR) looping yourself, e.g. by using a table variable:
-- declare temporary work table
DECLARE #RbarTable TABLE (CategoryName VARCHAR(50))
-- insert values into temporary work table
INSERT INTO #RbarTable(CategoryName)
SELECT
XTbl.Cats.value('.', 'varchar(50)')
FROM
#XmlVariable.nodes('/parent_node/category') AS XTbl(Cats)
-- declare a single category
DECLARE #CategoryNameToBeInserted VARCHAR(50)
-- get the first category
SELECT TOP 1 #CategoryNameToBeInserted = CategoryName FROM #RbarTable
-- as long as we have data
WHILE #CategoryNameToBeInserted IS NOT NULL
BEGIN
-- execute your stored procedure here.....
EXEC sp_executesql N'dbo.YourStoredProcedure #CategoryName',
N'#CategoryName VARCHAR(50)',
#CategoryName = #CategoryNameToBeInserted
-- delete the category we just inserted from the temporary work table
DELETE FROM #RbarTable WHERE CategoryName = #CategoryNameToBeInserted
-- see if we still have more categories to insert
SET #CategoryNameToBeInserted = NULL
SELECT TOP 1 #CategoryNameToBeInserted = CategoryName FROM #RbarTable ORDER BY CategoryName
END
With XML in SQL Server there's always more than one way to do it. Depending on the size of your XML doc and the number of times you're querying it, you could be best off using sp_xml_preparedocument which parses the document, gives you a handle to reference it, and then you can query it as many times and ways as you want to. Here's how you do that:
declare #xml xml = '
<parent_node>
<category>Low</category>
<category>Medium</category>
<category>High</category>
</parent_node>'
declare #xml_handle int
exec sp_xml_preparedocument #xml_handle output, #xml
select value from openxml(#xml_handle, '/parent_node/category', 2) with (value varchar(100) 'text()') x
exec sp_xml_removedocument #xml_handle

How do I execute a stored procedure once for each row returned by query?

I have a stored procedure that alters user data in a certain way. I pass it user_id and it does it's thing. I want to run a query on a table and then for each user_id I find run the stored procedure once on that user_id
How would I write query for this?
use a cursor
ADDENDUM: [MS SQL cursor example]
declare #field1 int
declare #field2 int
declare cur CURSOR LOCAL for
select field1, field2 from sometable where someotherfield is null
open cur
fetch next from cur into #field1, #field2
while ##FETCH_STATUS = 0 BEGIN
--execute your sproc on each row
exec uspYourSproc #field1, #field2
fetch next from cur into #field1, #field2
END
close cur
deallocate cur
in MS SQL, here's an example article
note that cursors are slower than set-based operations, but faster than manual while-loops; more details in this SO question
ADDENDUM 2: if you will be processing more than just a few records, pull them into a temp table first and run the cursor over the temp table; this will prevent SQL from escalating into table-locks and speed up operation
ADDENDUM 3: and of course, if you can inline whatever your stored procedure is doing to each user ID and run the whole thing as a single SQL update statement, that would be optimal
try to change your method if you need to loop!
within the parent stored procedure, create a #temp table that contains the data that you need to process. Call the child stored procedure, the #temp table will be visible and you can process it, hopefully working with the entire set of data and without a cursor or loop.
this really depends on what this child stored procedure is doing. If you are UPDATE-ing, you can "update from" joining in the #temp table and do all the work in one statement without a loop. The same can be done for INSERT and DELETEs. If you need to do multiple updates with IFs you can convert those to multiple UPDATE FROM with the #temp table and use CASE statements or WHERE conditions.
When working in a database try to lose the mindset of looping, it is a real performance drain, will cause locking/blocking and slow down the processing. If you loop everywhere, your system will not scale very well, and will be very hard to speed up when users start complaining about slow refreshes.
Post the content of this procedure you want call in a loop, and I'll bet 9 out of 10 times, you could write it to work on a set of rows.
You can do it with a dynamic query.
declare #cadena varchar(max) = ''
select #cadena = #cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(#cadena);
Something like this substitutions will be needed for your tables and field names.
Declare #TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare #i Int, #MaxI Int, #UserID nVarchar(50)
Insert into #TableUser
Select User_ID
From Users
Where (My Criteria)
Select #MaxI = ##RowCount, #i = 1
While #i <= #MaxI
Begin
Select #UserID = UserID from #TableUsers Where MyRowCount = #i
Exec prMyStoredProc #UserID
Select
#i = #i + 1, #UserID = null
End
Use a table variable or a temporary table.
As has been mentioned before, a cursor is a last resort. Mostly because it uses lots of resources, issues locks and might be a sign you're just not understanding how to use SQL properly.
Side note: I once came across a solution that used cursors to update
rows in a table. After some scrutiny, it turned out the whole thing
could be replaced with a single UPDATE command. However, in this case,
where a stored procedure should be executed, a single SQL-command
won't work.
Create a table variable like this (if you're working with lots of data or are short on memory, use a temporary table instead):
DECLARE #menus AS TABLE (
id INT IDENTITY(1,1),
parent NVARCHAR(128),
child NVARCHAR(128));
The id is important.
Replace parent and child with some good data, e.g. relevant identifiers or the whole set of data to be operated on.
Insert data in the table, e.g.:
INSERT INTO #menus (parent, child)
VALUES ('Some name', 'Child name');
...
INSERT INTO #menus (parent,child)
VALUES ('Some other name', 'Some other child name');
Declare some variables:
DECLARE #id INT = 1;
DECLARE #parentName NVARCHAR(128);
DECLARE #childName NVARCHAR(128);
And finally, create a while loop over the data in the table:
WHILE #id IS NOT NULL
BEGIN
SELECT #parentName = parent,
#childName = child
FROM #menus WHERE id = #id;
EXEC myProcedure #parent=#parentName, #child=#childName;
SELECT #id = MIN(id) FROM #menus WHERE id > #id;
END
The first select fetches data from the temporary table. The second select updates the #id. MIN returns null if no rows were selected.
An alternative approach is to loop while the table has rows, SELECT TOP 1 and remove the selected row from the temp table:
WHILE EXISTS(SELECT 1 FROM #menuIDs)
BEGIN
SELECT TOP 1 #menuID = menuID FROM #menuIDs;
EXEC myProcedure #menuID=#menuID;
DELETE FROM #menuIDs WHERE menuID = #menuID;
END;
Can this not be done with a user-defined function to replicate whatever your stored procedure is doing?
SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition
where udfMyFunction is a function you make that takes in the user ID and does whatever you need to do with it.
See http://www.sqlteam.com/article/user-defined-functions for a bit more background
I agree that cursors really ought to be avoided where possible. And it usually is possible!
(of course, my answer presupposes that you're only interested in getting the output from the SP and that you're not changing the actual data. I find "alters user data in a certain way" a little ambiguous from the original question, so thought I'd offer this as a possible solution. Utterly depends on what you're doing!)
I like the dynamic query way of Dave Rincon as it does not use cursors and is small and easy. Thank you Dave for sharing.
But for my needs on Azure SQL and with a "distinct" in the query, i had to modify the code like this:
Declare #SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines
SELECT #SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' +
convert(varchar(8),tenantid) + ';'
from Dim_Machine
where iscurrent = 1
FOR XML PATH(''))
--for debugging print the sql
print #SQL;
--execute the generated sql script
exec sp_executesql #SQL;
I hope this helps someone...

T-SQL EXEC and scope

Let's say I have a stored procedure with this in its body:
EXEC 'INSERT INTO ' + quotename(#table) ' blah...'
SELECT IDENT_CURRENT('' + #table + '')
Is IDENT_CURRENT() guaranteed to get the identity of that row INSERTed in the EXEC? IDENT_CURRENT() "returns the last identity value generated for a specific table in any session and any scope", but the scope is different within the EXEC than the stored procedure, right?
I want to make sure that if the stored procedure is being called multiple times at once, the correct identity is SELECTed.
EDIT: Or do I need to do both the INSERT and SELECT within the EXEC, like so:
declare #insert nvarchar
set #insert =
'INSERT INTO ' + quotename(#table) ' blah...' +
'SELECT IDENT_CURRENT(''' + #table + ''')'
EXEC #insert
And if that's the case, how do I SELECT the result of the EXEC if I want to continue with more code in T-SQL? Like this (although it's obviously not correct):
declare #insert nvarchar
set #insert =
'INSERT INTO ' + quotename(#table) ' blah...' +
'SELECT IDENT_CURRENT(''' + #table + ''')'
declare #ident int
set #ident = EXEC #insert
-- more code
SELECT * FROM blah
UPDATE: In the very first snippet, if I SELECT SCOPE_IDENTITY() instead of using IDENT_CURRENT(), NULL is returned by the SELECT. :(
Try
EXEC 'INSERT INTO ' + quotename(#table) ' blah...; SELECT ##IDENTITY'
or better, according to this
EXEC 'INSERT INTO ' + quotename(#table) ' blah...; SELECT SCOPE_IDENTITY()'
According to Microsoft's T-SQL docs:
IDENT_CURRENT is similar to the SQL
Server 2000 identity functions
SCOPE_IDENTITY and ##IDENTITY. All
three functions return last-generated
identity values. However, the scope
and session on which last is defined
in each of these functions differ:
IDENT_CURRENT returns the last
identity value generated for a
specific table in any session and any
scope.
##IDENTITY returns the last identity
value generated for any table in the
current session, across all scopes.
SCOPE_IDENTITY returns the last
identity value generated for any table
in the current session and the current
scope.
So I would say, no, IDENT_CURRENT does not guarantee to give you back the right value. It could be the last IDENTITY value inserted in a different session.
I would make sure to use SCOPE_IDENTITY instead - that should work reliably.
Marc
http://blog.sqlauthority.com/2009/03/24/sql-server-2008-scope_identity-bug-with-multi-processor-parallel-plan-and-solution/
There is a bug in SCOPE_IDENTITY() I have switched my stored procedures over to the methodology used to retrieve default values from an insert:
declare #TheNewIds table (Id bigint, Guid uniqueidentifier)
insert [dbo].[TestTable] output inserted.Id, inserted.Guid into #TheNewIds
values (default);
select #Id = [Id], #Guid = [Guid] from #TheNewIds;
I think Scope_Identity() is what you're looking for, which will give you the most recent identify in the current scope.
I'd like to chip in my favourite solution by using OUTPUT keyword. Since INSERT can support multiple rows at a time, we would want to know the identities inserted. Here goes:
-- source table
if object_id('Source') is not null drop table Source
create table Source
(
Value datetime
)
-- populate source
insert Source select getdate()
waitfor delay '00:00.1'
insert Source select getdate()
waitfor delay '00:00.1'
insert Source select getdate()
select * from Source -- test
-- destination table
if object_id('Destination') is null
create table Destination
(
Id int identity(1, 1),
Value datetime
)
-- tracking table to keep all generated Id by insertion of table Destination
if object_id('tempdb..#Track') is null
create table #Track
(
Id int
)
else delete #Track
-- copy source into destination, track the Id using OUTPUT
insert Destination output inserted.Id into #Track select Value from Source
select Id from #Track -- list out all generated Ids
Go ahead to run this multiple times to feel how it works.