SQL Server stored procedure only works with locally re-declared variables; not variables passed to the procedure - sql-server

I have edited my SQL code blocks to more accurately show what is going on
Say I have a simple stored procedure:
CREATE PROCEDURE [DBO].[FOO]
(#VARIABLE VARCHAR(500))
AS
BEGIN
SELECT AVG(BAR)
FROM MYTABLE
WHERE THING = #VARIABLE AND RTRIM(LTRIM(THING)) <> ''
END
When I call this stored procedure from my classic ASP page; which in this case would be with:
Set foo = Server.CreateObject("ADODB.RecordSet")
curCmd = "Foo 'MYVARIABLE'"
foo.Open curCmd, connectionString
I get this error (on the same line as the page opens the foo object):
Arithmetic overflow error converting varchar to data type numeric.
If I call the stored procedure manually in the terminal (IDE?); then it works fine.
Also if I recreate the stored procedure as the following:
CREATE PROCEDURE [DBO].[FOO]
(#VARIABLE VARCHAR(500))
AS
BEGIN
DECLARE #VARIABLE2 VARCHAR(500) = #VARIABLE
SELECT AVG(BAR)
FROM MYTABLE
WHERE THING = #VARIABLE2 AND RTRIM(LTRIM(THING)) <> ''
END
Then the stored procedure runs fine.
I have tried dropping and recreating the stored procedure (without using the re-declaration trick), but it does not fix the issue.
*As an aside; there is validation on the data being inserted into the table to ensure that only numbers (integers) are being entered for the THING field. The THING field can also be blank; hence the where clause.
I basically have two questions:
Why does re-declaring the same variable type with the same data fix the issue?
Is there a way I can fix my problem without using this silly "re-declaration" trick?
Thanks in advance for any help with this.

I think you can get the same error if you use begin/end:
CREATE PROCEDURE [DBO].[FOO] (
#VARIABLE VARCHAR(500)
)
AS
BEGIN
DECLARE #VARIABLE2 VARCHAR(500) = #VARIABLE;
SELECT AVG(BAR) FROM MYTABLE WHERE THING = #VARIABLE2;
END;
Then, both statements will be part of the stored procedure body and you can work on fixing the data so it will work.

Related

How to systematically get stored procedure (SP) parameter names and their values INSIDE the SP execution

As described in title, I am trying to systematically get stored procedure pararameter names and their corresponding values inside the execution of the proper stored procedure.
First point, which is taking stored procedure parameter names, is easy using table [sys].[all_parameters] and the stored procedure name. However, getting the actual values of these parameters is the difficult part, specially when you are not allowed to use table [sys].[dm_exec_input_buffer] (as a developer, I am not allowed to read this table, since it is a system administrator table).
Here is the code I have so far, which I am sure can serve you as a template:
CREATE PROCEDURE [dbo].[get_proc_params_demo]
(
#number1 int,
#string1 varchar(50),
#calendar datetime,
#number2 int,
#string2 nvarchar(max)
)
AS
BEGIN
DECLARE #sql NVARCHAR(MAX);
DECLARE #ParameterNames NVARCHAR(MAX) = ( SELECT STRING_AGG([Name], ',') FROM [sys].[all_parameters] WHERE OBJECT_ID = OBJECT_ID('[dbo].[get_proc_params_demo]') )
SET #sql = N'SELECT ' + #ParameterNames;
DECLARE GetParameterValues CURSOR FOR
SELECT DISTINCT [Name] FROM [sys].[all_parameters] WHERE OBJECT_ID = OBJECT_ID('[dbo].[get_proc_params_demo]');
OPEN GetParameterValues;
DECLARE #param_values NVARCHAR(MAX) = NULL
DECLARE #StoredProcedureParameter NVARCHAR(MAX)
FETCH NEXT FROM GetParameterValues INTO #StoredProcedureParameter;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #param_values = 'ISNULL('+#param_values+','')'+#StoredProcedureParameter+','
EXEC(#param_values)
FETCH NEXT FROM GetParameterValues INTO #StoredProcedureParameter;
END;
CLOSE GetParameterValues;
DEALLOCATE GetParameterValues;
SET #param_values = LEFT(#param_values, LEN(#param_values) - 1)
EXEC sp_executesql #sql,#ParameterNames,#param_values;
END
EXEC [dbo].[get_proc_params_demo]
#number1=42,
#string1='is the answer',
#calendar='2019-06-19',
#number2=123456789,
#string2='another string'
This is my approach trying to dynamically get parameter actual values inside a cursor, but it does not work, and I am clueless so far. I know it is quite rudimentary, and I am happy to hear other approaches. To be fair, I don't know if this problem is even possible to solve without system tables, but it would be great.
EDIT: This is an attempt to get a generic code that works on any stored procedure. You do not want to hardcode any parameter name. The only input you have is the stored procedure name via OBJECT_NAME(##PROCID)

Declaring DATETIME in Stored proc MS SQL

I am trying to get System Datetime for a column when a new row is inserted or updated into a table using stored Proc in MS SQL. How can I achieve it?
I have tried below code
CREATE PROCEDUCE test_Cl_INSERT
#SRC_ID int,
#CREATED_BY datatime
AS
BEGIN
INSERT into dbo.CL_Batch(SRC_ID, Created_BY)
VALUES(#SRC_ID, CURRENT_TIMESTAMP)
END
EXEC dbo.test_Cl_INSERT
#SRC_ID=44
ERROR : #CREATED_BY parameter missing
This will work:
CREATE PROCEDURE test_Cl_INSERT
#SRC_ID int
AS
BEGIN
INSERT into dbo.CL_Batch(SRC_ID, Created_BY)
VALUES(#SRC_ID, CURRENT_TIMESTAMP)
END
EXEC dbo.test_Cl_INSERT
#SRC_ID=44
Your procedure signature is:
CREATE PROCEDUCE test_Cl_INSERT
#SRC_ID int,
#CREATED_BY datatime
You attempt to execute as:
EXEC dbo.test_Cl_INSERT #SRC_ID=44
Do you see something missing? You should. Your procedure has 2 parameters but you provide only 1 when you attempt to execute it. That is your problem. As already noted, you don't use that paramter within the logic of the procedure so why does it exist at all?
You must execute your procedure like this:
EXEC dbo.test_Cl_INSERT #SRC_ID=44, #CREATED_BY = '20201124 12:49';
Notice I just assigned a random value to the parameter since it (the parameter) is not used within your procedure code. That solves the question you ask. However, you have more important issues to consider.

No result rowset is associated with the execution of this query

I am creating an Execute SQL task in SSIS 2016 which calls an insert stored procedure. I am trying to return the id of the newly created row in the output parameter but facing the following error.
No result rowset is associated with the execution of this query
I had set the SQL Server Profiler on to see what was generated and it was as follows
declare #p4 int
set #p4=NULL
exec sp_executesql N'Exec [dbo].[InsertPkgAudit] #P1,#P2',N'#P1 varchar(16),#P2 int OUTPUT','CoreReferenceETL',#p4 output
select #p4
If I execute the following it manually it works
DECLARE #auditId INT;
EXEC [dbo].[InsertPkgAudit] #packageName = 'CoreReferenceETL', #auditId = #auditId OUTPUT;
PRINT #auditId;
So it is clear that the stored procedure is fine but some problem with the way its called in SSIS. Could somebody help ?
The Execute SQL task contains the following statement
Exec [dbo].[InsertPkgAudit] #packageName =?, #auditId = ?
The parameter mapping is as follows
The result pane is as follows
The stored procedure is as follows:
CREATE Procedure [dbo].[InsertPkgAudit]
#packageName varchar(100),
#auditId int output
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[PkgAudit] ([PackageName], [StartTime])
VALUES (#packageName, GETDATE());
SET #auditId = SCOPE_IDENTITY();
END
The table structure is as follows
You have told SSIS that your procedure returns a result set. But it doesn't. It populates an OUTPUT parameter instead.
You can either change your proc to return a resultset, or you can modify the Execute task and
Specify No Result Set
Change the query to this:
`Exec [dbo].[InsertPkgAudit] #packageName =?, #auditId = ? OUTPUT`
I just had a similar issue and while looking for some sort of solution I came across this old post. I wasn't able to find the solution online but, here is how I resolved my issue. I hope this helps folks in the future.
If you really need to get the data passed via RowSet, you will need to select as
'ColumnName'.
Declare #fname varchar(50)
Declare #lname varchar(50)
set #fname ='John'
set #lname= 'Doe'
select #fname, #lname--without column name
select #fname as 'firstName', #lname as 'LastName'--with column name
Here is how they would show up in the results.
You can now map the result to proper variable.

TSQLT Returning Results from a Stored Procedure

In TSQLT, I'm trying to return a result from a stored procedure and add it to a variable so that I can assert if it matches my expected result.
I've seen loads of examples of returning results from functions but none where a stored procedure is called.
Does anybody have examples that they could share?
Thanks in advance
If you want to get a variable back from a stored procedure one way to do this is to use an out parameter
CREATE PROC MyTest
(#myVar int output)
AS
BEGIN
SET #myVar = 10
END
GO
DECLARE #x int
EXEC MyTest #myVar=#x output
SELECT #x
If you are getting a result set back from the stored procedure, here is an example from a tSQLt test that I wrote. I haven't bothered with the whole test because this should give you what you need.
CREATE TABLE #Actual (SortOrder int identity(1,1),LastName varchar(100), FirstName varchar(100), OrderDate datetime, TotalQuantity int)
-- Act
INSERT #Actual (LastName, FirstName, OrderDate, TotalQuantity)
EXEC Report_BulkBuyers #CurrentDate=#CurrentDate
The trick here is that you have to create the #actual table first. It should contain the same columns as what is returned from the stored procedure.
Just as an aside, you may have noticed I have a SortOrder column in the #actual table. This is because I was interested in testing the order of the data returned for this specific report. EXEC tSQLt.AssertEqualsTable will match rows like for like, but does not match the order in which the rows appear in the expected and actual so the way to ensure the order is to add a SortOrder column (which is an identity column) to both the #expected and #actual
Have a look here:
http://technet.microsoft.com/en-us/library/ms188655.aspx
Lots of examples about returning values from a stored procedure. At the bottom of the page there is also an example about evaluating a return code.
its actually really simple.
declare #variable int
exec #variable = _Stored_Procedure

EF4 - The selected stored procedure returns no columns

I have query in a stored procedure that calls some linked servers with some dynamic SQL. I understand that EF doesn't like that, so I specifically listed all the columns that would be returned. Yet, it still doesn't like that. What am I doing wrong here? I just want EF to be able to detect the columns returned from the stored procedure so I can create the classes I need.
Please see the following code that makes up the last lines of my stored procedure:
SELECT
#TempMain.ID,
#TempMain.Class_Data,
#TempMain.Web_Store_Class1,
#TempMain.Web_Store_Class2,
#TempMain.Web_Store_Status,
#TempMain.Cur_1pc_Cat51_Price,
#TempMain.Cur_1pc_Cat52_Price,
#TempMain.Cur_1pc_Cat61_Price,
#TempMain.Cur_1pc_Cat62_Price,
#TempMain.Cur_1pc_Cat63_Price,
#TempMain.Flat_Length,
#TempMain.Flat_Width,
#TempMain.Item_Height,
#TempMain.Item_Weight,
#TempMain.Um,
#TempMain.Lead_Time_Code,
#TempMain.Wp_Image_Nme,
#TempMain.Wp_Mod_Dte,
#TempMain.Catalog_Price_Chg_Dt,
#TempMain.Description,
#TempMain.Supersede_Ctl,
#TempMain.Supersede_Pn,
TempDesc.Cust_Desc,
TempMfgr.Mfgr_Item_Nbr,
TempMfgr.Mfgr_Name,
TempMfgr.Vendor_ID
FROM
#TempMain
LEFT JOIN TempDesc ON #TempMain.ID = TempDesc.ID
LEFT JOIN TempMfgr ON #TempMain.ID = TempMfgr.ID
EF doesn't support importing stored procedures which build result set from:
Dynamic queries
Temporary tables
The reason is that to import the procedure EF must execute it. Such operation can be dangerous because it can trigger some changes in the database. Because of that EF uses special SQL command before it executes the stored procedure:
SET FMTONLY ON
By executing this command stored procedure will return only "metadata" about columns in its result set and it will not execute its logic. But because the logic wasn't executed there is no temporary table (or built dynamic query) so metadata contains nothing.
You have two choices (except the one which requires re-writing your stored procedure to not use these features):
Define the returned complex type manually (I guess it should work)
Use a hack and just for adding the stored procedure put at its beginning SET FMTONLY OFF. This will allow rest of your SP's code to execute in normal way. Just make sure that your SP doesn't modify any data because these modifications will be executed during import! After successful import remove that hack.
Adding this Non-Logical block of code solved the problem. Even though it will never Hit
IF 1=0 BEGIN
SET FMTONLY OFF
END
Why does my typed dataset not like temporary tables?
http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataset/thread/fe76d511-64a8-436d-9c16-6d09ecf436ea/
Or you can create a User-Defined Table Type and return that.
CREATE TYPE T1 AS TABLE
( ID bigint NOT NULL
,Field1 varchar(max) COLLATE Latin1_General_CI_AI NOT NULL
,Field2 bit NOT NULL
,Field3 varchar(500) NOT NULL
);
GO
Then in the procedure:
DECLARE #tempTable dbo.T1
INSERT #tempTable (ID, Field1, Field2, Field3)
SELECT .....
....
SELECT * FROM #tempTable
Now EF should be able to recognize the returned columns type.
As some others have noted, make sure the procedure actually runs. In particular, in my case, I was running the procedure happily without error in SQL Server Management Studio completely forgetting that I was logged in with admin rights. As soon as I tried running the procedure using my application's principal user I found there was a table in the query that that user did not have permission to access.
Interesting side note: Had the same problem which I first solved by using Table Variables, rather than Temp Tables (just for the import). That wasn't particularly intuitive to me, and threw me off when initially observing my two SProcs: one using Temp tables and one with Table Variables.
(SET FMTONLY OFF never worked for me, so I just changed my SProcs temporarily to get the column info, rather than bothering with the hack on the EF side just as an FYI.)
My best option was really just manually creating the complex type and mapping the function import to it. Worked great, and the only difference ended up being that an additional FactoryMethod to create the properties was included in the Designer.
What I would add is:
That the import also fails if the stored procedures has parameters and returns no result set for the default parameter values.
My stored procedure had 2 float parameters and would not return anything when both parameters are 0.
So in order to add this stored procedure to the entity model, I set the value of these parameters in the stored procedure so that it is guaranteed to return some rows, no matter what the parameters actually are.
Then after adding this stored procedure to the entity model I undid the changes.
both solutions :
1- Define the returned complex type manually (I guess it should work)
2- Use a hack and just for adding the stored procedure put at its beginning SET FMTONLY OFF.
not working with me in some procedure however it worked with other one!
my procedure ends with this line:
SELECT machineId, production [AProduction]
, (select production FROM #ShiftBFinalProd WHERE machineId = #ShiftAFinalProd.machineId) [BProduction]
, (select production FROM #ShiftCFinalProd WHERE machineId = #ShiftAFinalProd.machineId) [CProduction]
FROM #ShiftAFinalProd
ORDER BY machineId
Thanks
In addition to what #tmanthley said, be sure that your stored procedure actually works by running it first in SSMS. I had imported some stored procedures and forgot about a couple dependent scalar functions, which caused EF to determine that the procedure returned no columns. Seems like a mistake I should have caught earlier on, but EF doesn't give you an error message in that case.
Entity Framework will try to get the columns by executing your stored procedure, passing NULL for every argument.
Please make sure that the stored procedure will return something under all the circumstances. Note it may have been smarter for Entity Framework to execute the stored proc with default values for the arguments, as opposed to NULLs.
ER does the following to get the metadata of the table:
SET FMTONLY ON
This will break your stored procedure in various circumstances, in particular, if it uses a temporary table.
So to get a result as complex type; please try by adding
SET FMTONLY OFF;
This worked for me - hope it works for you too.
Referred from https://social.msdn.microsoft.com/Forums/en-US/e7f598a2-6827-4b27-a09d-aefe733b48e6/entity-model-add-function-import-stored-procedure-returns-no-columns?forum=adodotnetentityframework
In my case adding SET NOCOUNT ON; at the top of the procedure fixed the problem. It's best practice anyway.
In my case SET FMTONLY OFF did not work. The method I followed is, I took backup of original stored procedure and replace with only column name like the below query.
Select Convert(max,'') as Id,Convert(max,'') as Name
After this change, create new function import, complex type in entity framework.
Once the function import and complex type is created, replace the above query with your original stored procedure.
SET FMTONLY OFF
worked for me for one of the procedure but failed for other procedure. Following steps helps me to resolve my problem
Within a stored procedure, I have created temporary table with the same column type and inserted all the data returned by dynamic query to temp table.
and selected the temp table data.
Create table #temp
(
-- columns with same types as dynamic query
)
EXEC sp_executeSQL #sql
insert into #temp
Select * from #temp
drop table #temp
Deleted existing complex type, import function and stored procedure instance for old stored procedure and updated entity model for current new procedure.
Edit the imported Function in entity modal for desired complex type, you will get all the column information there which is not getting for previous stored procedure.
once you have done with the type creation you can delete the temporary table from stored procedure and then refresh Entity Framework.
In Entity framework, while getting column information the sql executes the procedure with passing null values in parameter. So I handled null case differently by creating a temp table with all the required columns and returning all the columns with no value when null is passed to the procedure.
In my procedure there was dynamic query, something like
declare #category_id int
set #category_id = (SELECT CATEGORY_ID FROM CORE_USER where USER_ID = #USER_ID)
declare #tableName varchar(15)
declare #sql VARCHAR(max)
declare #USER_IDT varchar(100)
declare #SESSION_IDT varchar(10)
IF (#category_id = 3)
set #tableName = 'STUD_STUDENT'
else if(#category_id = 4)
set #tableName = 'STUD_GUARDIAN'
if isnull(#tableName,'')<>''
begin
set #sql = 'SELECT [USER_ID], [FIRST_NAME], SCHOOL_NAME, SOCIETY_NAME, SCHOOL_ID,
SESSION_ID, [START_DATE], [END_DATE]
from #tableName
....
EXECUTE (#sql)
END
ELSE
BEGIN
SELECT * from #UserPrfTemp
END
I was not getting the column information in
my case after using the set FMTONLY OFF trick.
This is temp table I created to get the blank data.
Now I am getting the column info
Create table #UserPrfTemp
(
[USER_ID] bigint,
[FIRST_NAME] nvarchar(60),
SCHOOL_NAME nvarchar(60),
SOCIETY_NAME nvarchar(200)
.....
}
I solved this problem creating a table variable and then returning from it.
DECLARE #VarTable TABLE (
NeededColumn1 VARCHAR(100),
NeededColumn2 INT,
NeededColumn3 VARCHAR(100)
)
...
--Fetch Data from Linked server here
...
INSERT INTO #VarTable (NeededColumn1,NeededColumn2,NeededColumn3)
SELECT Column1, Column2, Column3
FROM #TempTable
SELECT * FROM #VarTable.
In that manner, your the SP result will be bounded to the table variable, which EF has access to.
I discovered a method that should help most people out whatever's happening.
Pull up your favourite SQL client and run the proc that you're trying to update with every parameter = null. Visual Studio is literally trying to do this when SET FMTONLY ON. Run a trace. You'll see.
You'll probably get an error, or unexpected data out. Fix that and your issue is fixed.
In my case the function read in JSON and failed because the JSON string was empty.
I just put something like
IF(#FooJSON IS NULL)
BEGIN
SELECT 1 VAR1, 2 VAR2;
END
ELSE
--OTHER LOGIC
That's probably an ugly solution, but I inherited this mess and we don't go into Ravenholm.
Change #Temp tables with WITH SQL EXPRESSION

Resources