EXEC procedure in dynamic SQL statement only does SELECTs and not INSERTs - sql

I have a stored procedure, let's call it stored procedure 'B'. Stored procedure 'B' calls stored procedure 'A' which returns a resultset that needs to be inserted into a temp table within stored procedure 'B', in order to do further mutations. Because of nested inserts, I have used OPENROWSET (and tried OPENQUERY too).
Now, it seems to work great! However, next to returning a resultset, stored procedure 'A' also does INSERTS in a table. The weird thing is, when stored procedure 'A' is executed from within stored procedure 'B', stored procedure 'A' only returns the resultset, and does NO insert at all. It just seems to skip the entire INSERT INTO statement. I have tried putting dummy SELECT 'test' breakpoints before and after the INSERT, and they are executed fine! How is this possible?
This query looks like this (I changed data and columns up a bit):
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'INSERT INTO #Temp (1,2,3)
SELECT * FROM OPENROWSET (
''SQLOLEDB'',
''Server=(local);TRUSTED_CONNECTION=yes;'',
''SET FMTONLY OFF EXECUTE StoredProcedureA
#Parameter1 = '''''+#InputValue1+'''''
,#Parameter_2 = '''''+#InputValue2+'''''
''
)'
EXEC(#SQL)
No errors are returned. The resultset (SELECT statement from procedure A) is correctly loaded into #Temp within procedure B. But the INSERT that is done within procedure A is not executed.
Does openquery/openrowset not allow INSERTS and only execute SELECT outputs? I thought, maybe its a security/rights issue? Is there any other way to workaround this issue?
Thanks in advance guys!

It is because you are using a temporary table denoted by #.
The scope of this table is ends when your nested stored procedure ends and the temporary table is dropped.
So the insert happens, the table just doesn't exist anymore.
If you create the table before starting your nested procedure you can solve this problem. You can just drop the table in you Procedure B if you really want it gone.

Related

Select results from stored procedure into a table

I have a stored procedure, usp_region and it has a select statement with 50 columns as the result set. This procedure is called by multiple other stored procedures in our application.
Most of the stored procedure pass a parameter to this procedure and display the result set that it returns. I have one stored procedure, usp_calculatedDisplay, that gets the columns from this stored procedure and inserts the values into a temp table and does some more calculations on the columns.
Here's a part of the code in usp_calculatedDisplay.
Begin Procedure
/* some sql statements */
Declare #tmptable
(
-- all the 50 columns that are returned from the usp_region procedure
)
Insert Into #tmptable
exec usp_region #regionId = #id
Select t.*, /* a few calculated columns here */
From #tmptable t
End of procedure
Every time I add a column to the usp_region procedure, I'll also have to make sure I have to add it to this procedure. Otherwise it breaks. It has become difficult to maintain it since it is highly possible for someone to miss adding a column to the usp_calculatedDisplay procedure when the column is added to the usp_region.
In order to overcome this problem, I decided to do this:
Select *
Into #tmptable
From OPENROWSET('SQLNCLI',
'Server=localhost;Trusted_Connection=yes;',
'EXEC [dbo].[usp_region]')
The problem is 'Ad Hoc Distributed Queries' component is turned off. So I can't use this approach to overcome this issue. I was wondering if there are any other ways of overcoming this problem. I would really appreciate any help. Thank you!
Every time I add a column to the usp_region procedure
SQL Server is a structured database and it does not meant to solve such cases that you need to change your structure every day.
If you add/remove columns so often then you probably did not choose the right type of database, and you better re-design your system.
It has become difficult to maintain it since it is highly possible for someone to miss adding a column to the usp_calculatedDisplay procedure when the column is added to the usp_region.
There are two simple solutions for this (1) using DDL Triggers - very bad idea but simple to implement and working. (2) Using my trick to select from stored procedure
Option 1: using DDL trigger
You can automate the entire procedure and ALTER the stored procedure usp_calculatedDisplay every time that the stored procedure usp_region is changed
https://learn.microsoft.com/en-us/sql/relational-databases/triggers/ddl-triggers
The basic approach is
CREATE OR ALTER TRIGGER NotGoodSolutionTrig ON DATABASE FOR ALTER_PROCEDURE AS BEGIN
DECLARE #var_xml XML = EVENTDATA();
IF(
#var_xml.value('(EVENT_INSTANCE/DatabaseName)[1]', 'sysname') = 'tempdb'
and
#var_xml.value('(EVENT_INSTANCE/SchemaName)[1]', 'sysname') = 'dbo'
and
#var_xml.value('(EVENT_INSTANCE/ObjectName)[1]', 'sysname') = 'usp_region'
)
BEGIN
-- Here you can parse the text of the stored procedure
-- and execute ALTER on the first SP
-- To make it simpler, you can design the procedure usp_region so the columns names will be in specific row or between to comment which will help us to find it
-- The code of the Stored Procedure which you need to parse is in the value of:
-- #var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
-- For example we can print it
DECLARE #SP_Code NVARCHAR(MAX)
SET #SP_Code = CONVERT(NVARCHAR(MAX), #var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
PRINT #SP_Code
-- In your case, you need to execute ALTER on the usp_calculatedDisplay procedure using the text from usp_region
END
END
Option 2: trick to select from stored procedure using sys.dm_exec_describe_first_result_set
This is simple and direct way to get what you need.
CREATE OR ALTER PROCEDURE usp_calculatedDisplay AS
-- Option: using simple table, so it will exists outsie the scope of the dynamic query
DROP TABLE IF EXISTS MyTable;
DECLARE #sqlCommand NVARCHAR(MAX)
select #sqlCommand = 'CREATE TABLE MyTable(' + STRING_AGG ([name] + ' ' + system_type_name, ',') + ');'
from sys.dm_exec_describe_first_result_set (N'EXEC usp_region', null,0)
PRINT #sqlCommand
EXECUTE sp_executesql #sqlCommand
INSERT MyTable EXECUTE usp_region;
SELECT * FROM MyTable;
GO
Note!!! Both solutions are not recommended in production. My advice is to avoid such needs by redesign your system. If you need to re-write 20 SP so do it and don't be lazy! Your goal should be what best for the database usage.

How to union records of two stored procedures without knowing columns

I have 2 stored procedures which returns same but unknown columns. I need to write a proc to combine results from both stored procedures. I tried OPENROWSET but problem is to provide the connection string in OPENROWSET function, even if I specify the connection string one time, it will be different for different environments and I think that will be the worst thing to change connection string each time I deploy the application in different environments or if the user is changed on server. Can someone help me to get this done in the best way.
I cannot write them as function since the procs are using temp tables.
Declare #connection nvarchar(200)
Declare #sql nvarchar(max)
Set #connection= 'Server=servername;initial
catalog=dbname;user=abc,password=xyz';
Set #sql='SELECT * INTO #temp1
FROM OPENROWSET(
''SQLNCLI'',
'''+ #connection + ''',
''EXEC sp_name '')'
Exec(#sql)
--- creating a temporary table
CREATE Table #Dynamic_Temp_Table (_field_only_for_create_ INT )
--- Addition of fields from the first recordset from the first procedure
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL=ISNULL(#SQL+',','ALTER TABLE #Dynamic_Temp_Table ADD ')+name+' '+system_type_name
FROM sys.dm_exec_describe_first_result_set('exec sp_proc_first', NULL, NULL)
order by column_ordinal
exec sp_executesql #SQL
--- Remove of the first unused column
ALTER TABLE #Dynamic_Temp_Table drop column _field_only_for_create_
--- Addition of the result from the first procedure
INSERT INTO #Dynamic_Temp_Table
exec sp_proc_first
--- Addition of the result from the second procedure
INSERT INTO #Dynamic_Temp_Table
exec sp_proc_second
--- result: exec sp_proc_first UNION ALL exec sp_proc_second
select * from #Dynamic_Temp_Table
--- result: exec sp_proc_first UNION exec sp_proc_second
select DISTINCT * from #Dynamic_Temp_Table
It is possible but not easy at all....
You can change your stored procedures to just create and populate a global temp tables (no select), then you can select both with an union.
To use OPENROWSET as you are doing it in your current approach you will need global temp table too, But as you mention that your stored procedures are using Temp Tables, OPENROWSET, OPENQUERY or sys.dm_exec_describe_first_result_set will not be able to determine the metadata to create the temp table.
Another option is to change your stored procedures to use variable tables instead of temp tables, then the metadata could be redetermined. #chrszcpl's answer https://stackoverflow.com/a/55632401/10932521 is a very good solution if you are able to do that.
If this is not possible (I assume that this isn't, otherwise the columns would't be unknown) because you are using dynamic sql in your procedures, or you simply can't touch those procedures for any reason, I think that the cheapest solution is create a third stored procedure which returns the dynamic columns definition that the other procedures will return..

SQL return the output of SELECT and not the output of another stored procedure

How do I return the result of the SELECT as the output of the stored procedure? Sorry I'm new to stored procedures!
In this query below I'm calling stored procedure spCuExt_ExtractLog and assigning the result to variable #StartDate. I then use this variable within the main stored procedure, in a SELECT statement. I need to return only the result of the SELECT statement from the main stored procedure:
-- Main stored procedure
BEGIN
DECLARE #StartDate DATETIME
EXEC #StartDate = spCuExt_ExtractLog 'Customers'
SELECT Id, [Name], LogoPath, IsDeleted
FROM dbo.Customers
WHERE RecordCreatedDateUTC>= #StartDate
END
This returns the result of the call to spCuExt_ExtractLog as well as the result of the SELECT statement but I want to output the result of the SELECT only.
How do I do this?
Put the results into a table variable instead:
create procedure dbo.usp_Child
as
begin
select N'Hello world!' as [message];
end;
go
create procedure dbo.usp_Main
as
begin;
declare #results table ([message] nvarchar(max));
insert into #results
execute dbo.usp_Child;
select N'success';
end;
go
execute dbo.usp_Main;
Here's a link to a pretty good document explaining all the different ways to solve your problem (although a lot of them can't be used since you can't modify the existing stored procedure.)
http://www.sommarskog.se/share_data.html

How to execute a stored procedure after it is created?

I'm trying to execute a stored procedure directly after its creation however it is not getting called. It looks like the stored procedure is not yet created during the execution call.
Here is how the script looks like:
CREATE PROCEDURE sp_Transfer_RegionData
AS
BEGIN
INSERT INTO Region (regionName)
SELECT column1
FROM openquery(ITDB, 'select * from db.table1')
END
EXEC sp_Transfer_RegionData
The script runs fine however the needed table is not populated. After replacing the execution part with:
IF OBJECT_ID('sp_Transfer_RegionData') IS NOT NULL
begin
exec [dbo].[sp_Transfer_RegionData]
print 'tada'
end
I could see that the stored procedure does not exist when it has to be executed. Couldn't find a solution for this in the internet...
So how to make the SQL script run sync so that the stored procedure would already exist during the execution part?
You need a GO after you created the SP otherwise you have created a recursive SP that calls itself "indefinitely" which is 32 times in SQL Server.
Maximum stored procedure, function, trigger, or view nesting level
exceeded (limit 32).
Try this:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE sp_Transfer_RegionData
AS
BEGIN
INSERT INTO Region (regionName)
SELECT column1
FROM openquery(ITDB, 'select * from db.table1')
END
GO
EXEC sp_Transfer_RegionData

What is the best way to assign the returned value of a stored proc to a variable in SQL?

I have a stored procedure that returns a valueI call it from other stored procedures that need to retrieve this value. The calling stored procedure is inside a transaction, the stored procedure that returns the value (and actually creates the value and stores it in a table that no other proc touches) is not inside its own transaction, but would be part of the caller's transaction.
The question is this, what is the most efficient way of retrieving the return value of the stored procedure and storing it in a variable in the calling proc?
Currently I have the following and I'm wondering if its very inefficient?
DECLARE #tmpNewValue TABLE (newvalue int)
INSERT INTO #tmpNewValue EXEC GetMyValue
DECLARE #localVariable int
SET #localVariable = (SELECT TOP 1 newvalue FROM #tmpNewValue )
Isn't there a more straight forward way of doing this? Isn't this an expensive (in terms of performance) way?
My stored proc doesn't have an output param, it just returns a value. Would using an output param be faster?
For what it's worth I'm using MS SQL Server 2005
If your getting a single return variable then yes this is innefficent you can do:
declare #localVariable int
exec #localVariable =GetMyValue
select #localVariable
See How to Share Data Between Stored Procedures
By some reasons 'exec #localVariable =GetMyValue' is not working for me (MS SQL 2005), it's always return 0 value (They have the same issue).
My opinion is:
if you can change stored procedure, add output parameter.
else if you can remove procedure, rewrite it as a function.
else use table variable, as you do.
Is this proc returning a rowset of 1 row and 1 column or no rowset at all and you just want to capture the returncode?
If you want just the returncode then use Josh's method otherwise use a OUTPUT parameter sicne it will be much faster than what you are doing now
To Explain what I mean run this code
use tempdb
go
create proc GetMyValue
as
select 1
go
create table #temp (id int)
declare #localVariable int
insert #temp
exec #localVariable =GetMyValue
select #localVariable,* from #temp
Try this:
create proc AvilableSeats
as
declare #v1 int,#v2 int
exec #v1= determinPath_Capacity 1,'Sat-Tue',32
exec #v2=Student_fGroup '6/12/2009'
select #v1-#v2