T-SQL EXEC and scope - sql

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.

Related

Read columns in SQL tables which are the result of another query

I need to check that all primary key columns do have all values in uppercase.
So, I have a first request which returns me the table-field pairs which are part of PK.
SELECT table_name, field_name FROM dico WHERE pkey > 0;
(dico is some table which gives that information. No need to look it up in the SQL Schema…)
And, for all those pairs tx/fx listed from that first query above, I need to look for values which would not be uppercased.
SELECT DISTINCT 't1', 'f1', f1 FROM t1 WHERE f1 <> UPPER(f1) UNION ALL
SELECT DISTINCT 't2', 'f2', f2 FROM t2 WHERE f2 <> UPPER(f2) UNION ALL
...
SELECT DISTINCT 'tn', 'fn', fn FROM tn WHERE fn <> UPPER(fn);
(I'm putting the table name and field name as "strings" in the output, so that I know from where the wrong values are coming.)
As you see, I do have the code for both requests, but I do not know how to combine them (if possible, in a generic way that would work for both SQL Server and Oracle).
Can you give me some idea on how to finish that?
One way that I could think of is to use a statement block that contains a loop.
Unfortunately, the structure of a statement block will be different for every different database system (the one for SQL Server will be different for Oracle).
I wrote an example using SQL Server further below (fiddle link is at: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=85cd786adf32247da1aa73c0341d1b72).
Just in case, the dynamic query gets very long (possibly longer than the limit of varchar, which is 8000 characters), SQL Server has varchar(max) that can hold up to 2GB (https://learn.microsoft.com/en-us/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-ver15). This can be used for #DynamicQuery, replacing VARCHAR(3000) in the example below (modified/alternative fiddle link, just to show that the data type really exists and can be used, is at: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=7fbb5d130aad35e682d8ce7ffaf09ede).
Please note that the example is not using your exact queries because I do not have access to the exact same data as the one you have (e.g. I cannot test the example using dico table because I do not have access to that table).
However, I made the example so that it uses a similar basic structure of logic from your queries, so that later on it can be customised to suit your exact need/scenario (e.g. by changing the table names and field names to match the ones that you use, as well as by adding the WHERE clause as you need).
In the example, your 1st query will be run immediately and the result will be handled by a cursor.
After that, a loop (using WHILE statement/structure) will loop through the cursor for the result of the 1st query to dynamically build the 2nd query (inserting the table names and the field names from the 1st query).
Note that at this point, the 2nd query is still being built, not being run yet.
Eventually, after the loop has finished, the resulting/compiled 2nd query will be run/executed (using the EXEC command).
-- START of test data creation.
create table TableA
( message varchar(200)
);
insert into TableA([message]) values ('abc');
insert into TableA([message]) values ('def');
create table TableB
( message varchar(200)
);
insert into TableB([message]) values ('ghi');
insert into TableB([message]) values ('jkl');
-- END of test data creation.
-- START of dynamic SQL
declare #TableAndFieldDetails CURSOR
declare #TableName VARCHAR(50)
declare #FieldName VARCHAR(50)
declare #DynamicQuery VARCHAR(3000) = ''
begin
SET #TableAndFieldDetails = CURSOR FOR
-- START of the 1st query
SELECT [INFORMATION_SCHEMA].COLUMNS.TABLE_NAME,
[INFORMATION_SCHEMA].COLUMNS.COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME LIKE '%message%'
-- END of the 1st query
-- START of dynamically building the 2nd query
OPEN #TableAndFieldDetails
FETCH NEXT FROM #TableAndFieldDetails INTO #TableName, #FieldName
WHILE ##FETCH_STATUS = 0
BEGIN
IF #DynamicQuery <> ''
BEGIN
SET #DynamicQuery += ' UNION ALL '
END
-- The one line right below is each individual part/element of the 2nd query
SET #DynamicQuery += 'SELECT ''' + #TableName + ''', ''' + #FieldName + ''', ' + #FieldName + ' FROM ' + #TableName
FETCH NEXT FROM #TableAndFieldDetails INTO #TableName, #FieldName
END
CLOSE #TableAndFieldDetails
DEALLOCATE #TableAndFieldDetails
-- END of dynamically building the 2nd query
EXEC (#DynamicQuery)
end
-- END of dynamic SQL

How to select newly inserted record(s) in MSSQL stored procedure in case of execution of dynamic insert script?

I want to select newly inserted record in my sql server database.
I know that the right solution for me is this:
DECLARE #OutputTbl TABLE (ID INT)
INSERT INTO MyTable(Name, Address, PhoneNo)
OUTPUT INSERTED.ID INTO #OutputTbl(ID)
VALUES ('Yatrix', '1234 Address Stuff', '1112223333')
But my question is about create insert script and execute it in specific stored procedure.
I wrote these two lines at the end of my stored procedure to insert my data to any table I need.
SET #sqlQuery = 'INSERT INTO ' + #tblName + '(' + #param_Collection + ') ' + 'OUTPUT INSERTED.' + #IDENT_Field + ' values('+ #value_Collection +')';
EXEC sys.sp_executesql #sqlQuery;
All variables declared before.
I don't know how to access the identity field to select proper newly inserted record(s).
No, you can't.
A table-valued parameter must be declared with the READONLY option. Thus you can't insert into it.
Even if you declare it in the dynamic SQL, you can insert the data into it, but you can't get the data from the table, because the table variable is isolated from the scope of the dynamic SQL.

How to use a variable in "Select [some calculations] insert into #NameOfTheTableInThisVariable"?

I have a procedure in which there are calculations being done and the final result is inserted into a permanent table. I want to remove the permanent table and I cannot use Temp table as well. So i want to use a dynamic table name, which is stored in a variable:
Current scenario:
Insert into xyz_table
Select col1,col2,sum(col3)
from BaseTable
(In reality, there are lot of columns and a lot of calculations)
What I want:
Select col1,col2,sum(col3) into #DynamicTableName
from BaseTable
where the name of the table would be dynamic in nature i.e.,
#DynamicTableName = 'xyz ' + cast(convert(date,getdate()) as nvarchar)+' '+convert(nvarchar(5),getdate(),108)
It will have date and time in its name every time the procedure is run.
I want to use this name in the "Select * into statement"
How can I achieve this?
i tried it with the some short code. But since my procedure has a lot of calculations and UNIONS , I cannot use that code for this. Any help would be appreciated.
declare #tablename nvarchar(30)= 'xyz ' + cast(convert(date,getdate()) as nvarchar)+' '+convert(nvarchar(5),getdate(),108)
declare #SQL_Statement nvarchar(100)
declare #SQL_Statement2 nvarchar(100)
declare #dropstatement nvarchar(100)
SET #SQL_Statement = N'SELECT * Into ' +'['+#tablename +'] '+'FROM '+ 'dimBranch'
print #SQL_Statement
EXECUTE sp_executesql #SQL_Statement
SET #SQL_Statement= N'select * from ' + '['+#tablename + '] '
print #SQL_Statement
EXECUTE sp_executesql #SQL_Statement
set #dropstatement = 'DROP TABLE' + '['+#tablename + '] '
PRINT #dropstatement
exec sp_executesql #dropstatement
Reason why I want this is because I use this procedure in ETL job as well as in SSRS report. And if someone runs the package and the SSRS report at the same time, the incorrect or weird data gets stored in the table. Therefore I need a dynamic name of the table with date and time.
You can't parameterize an identifier in SQL, only a value
--yes
select * from table where column = #value
--no
select * from #tablename where #columnname = #value
The only thin you can do to make these things dynamic is to build an sql string and execute it dynamically, but your code is already doing this with sp_executesql
More telling is your complaint at the bottom of your question, that if the procedure is invoked simultaneously it gives problems. Perhaps you should consider using local table variables for temporary data storage that the report is using rather than pushing data back into the db
DECLARE #temp TABLE(id INT, name varchar100);
INSERT INTO #temp SELECT personid, firstname FROM person;
-- work with temp data
select count(*) from #temp;
--when #temp goes out of scope it is lost,
--no other procedure invoked simultaneously can access this procedure'a #temp
Consider a local temp table, which is automatically session scoped without the need for dynamic SQL. For example:
SELECT *
INTO #YourTempTable
FROM dimBranch;
The local temp table will automatically be dropped when the proc completes so there is no need for an explict drop in the proc code.

How can I declare a table variable with a decimal variable as the identity seed? [duplicate]

This question already has answers here:
Programmatically set identity seed in a table variable
(3 answers)
Closed 9 years ago.
I'm using this, but I can't figure out why this doesn't work. SSMS won't give me a useful message other than syntax incorrect:
DECLARE #columnSeed DECIMAL
SELECT #columnSeed = MAX([seeded_column]) + 1 FROM [table] (nolock) WHERE [conditions]
DECLARE #Temp_Table TABLE ([seeded_column] varchar(35) IDENTITY(#columnSeed, 1), [more columns])
I want to take the maximum value from a column in one table and create a temporary table variable with an identity column seeded with that previous maximum value.
Edit: OK, after digging around for into about dynamic SQL I think I've got what should work, but it still isn't:
DECLARE #columnSeed DECIMAL
[#columnSeed set properly]
EXECUTE sp_executesql
N'DECLARE #Temp TABLE (seeded_column decimal IDENTITY(#seed, 1) NOT NULL [more columns])',
N'#seed decimal',
#seed = #columnSeed;
All the info I get now is that I've incorrect syntax near '#seed'
You can't use a variable as a seed. It is invalid syntax. The table variable is already implicitly created before the batch is executed and the variable assigned anyway.
The only way of doing this would be to concatenate the desired query and execute it. All usages of the table variable would need to be in the child scope.
DECLARE #columnSeed DECIMAL(18,0) = 10
DECLARE #sql NVARCHAR(MAX) = N'DECLARE #Temp TABLE (seeded_column decimal IDENTITY(' + CAST(#columnSeed AS NVARCHAR(19)) +', 1) NOT NULL)
INSERT INTO #Temp DEFAULT VALUES;
SELECT * FROM #Temp;'
EXECUTE sp_executesql
#sql,
N'#seed decimal',
#seed = #columnSeed;
I'm sure there is a better way of doing whatever it is you are doing anyway though.
You could just declare the table variable in the outer scope with a seed of 0 and add the desired offset to your SELECT queries from it for example.
DECLARE #columnSeed DECIMAL(18,0) = 10
DECLARE #Temp TABLE (seeded_column decimal(18,0) IDENTITY(0, 1) NOT NULL)
INSERT INTO #Temp DEFAULT VALUES;
INSERT INTO #Temp DEFAULT VALUES;
SELECT #columnSeed + seeded_column AS psuedo_seeded_column
FROM #Temp;
Though the whole need for this seems suspect. You shouldn't normally care what the IDENTITY values are. If this is to prepare data that later is inserted into the table you are calculating #columnSeed from maybe just inserting it and using the OUTPUT clause to get the ID values inserted might be more appropriate and less at risk of concurrency issues.
I think you can't use parameters in DDL. In other words, you won't be able to use #seed in the IDENTITY clause. Convert the seed to a string and shove it into your DDL manually. Something like this should work. (I don't have a SQL Server instance handy, so my apologies if there are any additional errors. The point is: Don't use parameters in DDL statements.)
DECLARE #columnSeed DECIMAL
DECLARE #sql NVARCHAR(1024)
[#columnSeed set properly]
SET #sql = N'DECLARE #Temp TABLE (seeded_column decimal IDENTITY(' || CONVERT(NVARCHAR, #seed) || N', 1) NOT NULL [more columns])';
EXECUTE sp_executesql #sql

Quickest way to clone row in SQL

I want to clone multiple tables' rows that have a single primary key (PrimKey). What's the quickest way to clone a row in SQL Server 2005?
Here's an example,
Clone PrimKey1 to get PrimKey2. So I try the following :
INSERT INTO PrimKeys
SELECT 'PrimKey2' AS PrimKey,*
FROM PrimKeys
WHERE PrimKey='PrimKey1'
But of course the issue here is, the column PrimKey gets repeated in the inner SELECT statement. Is there any other way similar in simplicity to the above?
Thank you all for your responses. I went ahead and wrote a function that handles that in my application. I don't use Stored Procs or Temp tables so I couldn't use a couple of valid answers posted by some of you.
You can run something like the stored procedure below to avoid typing out all of the column names. The example below assumes an int, but you can swap the key type out for any data type.
create procedure [CloneRow]
#tableName varchar(max),
#keyName varchar(max),
#oldKeyId int,
#newTableId int output
as
declare #sqlCommand nvarchar(max),
#columnList varchar(max);
select #columnList = coalesce(#columnList + ',','') + sys.columns.name
from sys.columns
where object_name(sys.columns.object_id) = #tableName
and sys.columns.name not in ( #keyName )
and is_computed = 0;
set #sqlCommand = 'insert into ' + #tableName + ' ( ' + #columnList + ') (' +
'select ' + #columnList + ' from ' + #tableName + ' where ' + #keyName + ' = #oldKeyId )'
exec sp_executesql #sqlCommand, N'#oldKeyId int', #oldKeyId = #oldKeyId
select #newTableId = ##identity -- note scope_identity() won't work here!
GO
You call it like this:
declare #newOrderId int
exec [CloneRow] 'orderTable', 'orderId', 625911, #newOrderId output
This is not the most beautiful solution, but I think it will work for you. First of all, you select your data into a temporary table with a "new" primary key and next you drop the old primary key column from the temp table and use the temp table to insert your "cloned" row.
SELECT
'PrimKey2' AS NewPrimKey,
*
INTO #TMP
FROM PrimKeys
WHERE PrimKey='PrimKey1';
ALTER TABLE #TMP DROP COLUMN PrimKey;
INSERT INTO PrimKeys
SELECT * FROM #TMP;
Not sure what do you mean by "multiple tables' rows that have a single primary key".
PRIMARY KEY, by definition, is UNIQUE.
To do your query you need to enumerate all columns:
INSERT
INTO PrimKeys (PrimKey, col1, col2, …)
SELECT 'PrimKey2' AS PrimKey, col1, col2, …
FROM PrimKeys
WHERE PrimKey = 'PrimKey1'
First, if you need to perform generic operations against a large number of tables, then dynamic SQL and the system tables are your friends.
Short of that, Hakan's solution will work for non-identity PKs. I would tighten it up to:
SELECT * INTO #TMP
FROM PrimKeys
WHERE PrimKey='PrimKey1';
UPDATE #TMP SET PrimeKey = 'PrimeKey2';
INSERT INTO PrimKeys
SELECT * FROM #TMP;
For identity PKs, change the UPDATE above to a DROP COLUMN:
SELECT * INTO #TMP
FROM PrimKeys
WHERE PrimKey=101;
ALTER TABLE #TMP DROP COLUMN PrimeKey;
INSERT INTO PrimKeys
SELECT * FROM #TMP;
If you are cloning from multiple tables, the fastest way will be to use a stored procedure, so that all the queries stay on the database, and you don't pay a price for communication between the client and the server.
Once you do that, then start unit-testing, to see how long it takes to do the operation. Then begin experimenting with changing it, based on suggestions you get, and see if you get any improvement.