How to create/drop function within stored procedure in SQL Server - sql-server

I have created a stored procedure where I an looping through data and inserting records based on the data. The inserts have the same format, but the data changes slightly.
The decision making is handled with IF/ELSE statements, and I have a dozen or so INSERT queries (one for each decision).
I'd like to put the INSERT into a function that I can just call with the variables that change.
I would like to create the function at the beginning of the stored procedure, and then DROP it at the end.
USE [DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Procedure [dbo].[DataParser]
AS
CREATE FUNCTION [dbo].[InsertFunc]
(#Number VARCHAR(40),
#Line_No INT,
#Topic VARCHAR(30),
#Note VARCHAR(4099),
#Bool VARCHAR(1))
RETURNS bit
AS
BEGIN
INSERT INTO Processing (Number, Line_No, Topic, Note, Activation_Date, Mandatory)
VALUES (#Number, #Line_No, #Topic, #Note,
CONVERT(VARCHAR(8), GETDATE(), 1), #Mandatory)
RETURN ##ROWCOUNT
END
...
...
Decision tree
...
...
InsertFunc(#oid, #lidCounter, 'StrVal1', #strVar2, 'Y')
Right now it will not let me save the stored procedure, and if I try to create the function outside of the stored procedure, I get the following error:
Msg 443, Level 16, State 15, Procedure DataParser, Line 12 [Batch Start Line 23]
Invalid use of a side-effecting operator 'INSERT' within a function.
Any help is appreciated.

The short answer is that in SQL Server you cannot perform INSERT / UPDATE / DELETE actions within a function.
Although I don't recommend an approach where you are creating and removing stored procedures dynamically, if you try your approach creating a stored procedure instead of a function it should work.

Below is stored procedure that may help you.
Create Table #Temp
(
Number varchar(40),
Line_No INT,
Topic VARCHAR(30),
Note VARCHAR(4099),
Bool VARCHAR(1),
Activation_Date datetime,
Mandatory varchar(10)
)
CREATE procedure [dbo].[InsertDataIntoTable]
(#Number VARCHAR(40),
#Line_No INT,
#Topic VARCHAR(30),
#Note VARCHAR(4099),
#Bool VARCHAR(1),
#Mandatory varchar(4))
AS
BEGIN
INSERT INTO #Temp (Number, Line_No, Topic, Note, Activation_Date, Mandatory)
VALUES (#Number, #Line_No, #Topic, #Note,
CONVERT(VARCHAR(8), GETDATE(), 1), #Mandatory)
END

Related

Create a whitelist of values to be used in a stored procedure parameter value

I have a stored procedure which takes a string parameter:
CREATE PROCEDURE sp_my_procedure
#param1 NVARCHAR(100)
AS
BEGIN
-- do stuff
END
However, not all string values are valid values for this parameter. I know I can do this:
CREATE PROCEDURE sp_my_procedure
#param1 NVARCHAR(100)
AS
BEGIN
IF #param1 NOT IN ('val1', 'val2', ...)
BEGIN
-- raise error and then return
END
-- do stuff
END
But this solution is both hard to read depending on where exactly I can put the check (what other checks need to be done, etc.) and relies on custom error numbers, which I would like to avoid (standardised is not always better, but most of the time it is).
Another solution I found is to create a user-defined data type and bind a rule to it:
CREATE RULE my_rule
AS
#val IN ('val1', 'val2', ...);
GO
CREATE TYPE RESTRICTED_STRING_TYPE
FROM NVARCHAR(100) NOT NULL;
GO
EXEC sp_bindrule 'my_rule', 'RESTRICTED_STRING_TYPE';
GO
But this solution is both
a) deprecated
b) not working as expected:
-- after type and rule has been created
DECLARE #val RESTRICTED_STRING_TYPE; -- this should fail because of the not null clause
SET #val = 'invalid value'; -- but even if not null is not there, this should definitely fail
PRINT(#val); -- nope, 'invalid value' is printed without error
I would like to do something like this:
CREATE PROCEDURE sp_my_procedure
#param1 NVARCHAR(100) IN ('val1', 'val2', ...)
AS
BEGIN
-- do stuff
END
But couldn't find the right syntax for it. Does SQL Server even support this functionality?
The method you use in your first example is the right one. What you could do, however, is create a table with the accepted values in there. Then you could use an EXISTS to check the value is valid against the table instead. This would make your procedure more succinct, and if you need to do the implement the validation in multiple procedures, it makes it more easily transferable, and adding a value to your validation table "updates" all the procedures.
So, in very simple terms:
CREATE TABLE dbo.RESTRICTED_STRING_VALUES (StringValue nvarchar(100);
INSERT INTO dbo.RESTRICTED_STRING_VALUES
VALUES (N'Val1'),
(N'Val2'),
(N'Val3');
GO
CREATE PROCEDURE dbo.my_procedure --Don't use "sp_" as a prefix! It's reserved by Microsoft.
#param1 NVARCHAR(100) --Doing so comes at a performance cost,
--and could result in the Proc simply not working after an update
AS
BEGIN
IF NOT EXISTS (SELECT 1 FROM dbo.RESTRICTED_STRING_VALUES WHERE StringValue = #param1)
BEGIN
--Raise error and then return
END;
--do stuff
END;
GO

What is the mandatory nature of User Defined Tables in Stored Procedures

At the company I work there are a range of stored procedures which take a user-defined table as an input parameter and is it happens this is not always needed for the procedure being used. In SSMS if write an Execute statement without defining the UDT I get the error line with a tooltip telling me the procedure expects the table to be provided, but if I try to run without providing the table it works just fine.
This makes me assume these are optional parameters even though they don't have a clear default like other input parameters would need? If that's the case how can you force them to be non-optional?
As it is for my company procedures the non-optional nature is preferable but I'd like to know why this is as a learning point and how to get around it please.
This SQL demonstrates my question:
CREATE TYPE Dummy_Table AS TABLE (ID INT, Name VARCHAR(50));
GO
CREATE PROCEDURE Dummy_Procedure #Mode VARCHAR(50), #Dummy_Table Dummy_Table READONLY
AS
BEGIN
SELECT #Mode
SELECT * FROM #Dummy_Table
END
GO
EXEC Dummy_Procedure #Mode = 'Dummy_Mode'
TVPs are optional, and they can produce some weird behavior if you are expecting records and don't get any. You could include some logic to check if there are any records in the table.
This article also helps:
https://msdn.microsoft.com/en-us/library/bb510489.aspx
CREATE TYPE Dummy_Table AS TABLE (ID INT, Name VARCHAR(50));
GO
ALTER PROCEDURE Dummy_Procedure #Mode VARCHAR(50), #Dummy_Table Dummy_Table READONLY
AS
BEGIN
SELECT #Mode
declare #test int
SELECT #test = COUNT(*) FROM #Dummy_Table
if #test = 0
select 'stop'
else
select 'continue'
END
GO

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

Forward slash in stored procedure parameter not recognised

I'm making this stored procedure call:
exec sp_executesql N'EXEC MyStoredProcedure
#MyId = #0, #MyVarField = #1',
N'#0 int, #1 nvarchar(4000)',
#0=2, #1='lll/kkk'
The stored procedure is just a simple select, looking for MyVarField = 'lll/kkk', but the / in the parameter seems to break it... it can't find the db row anyway.
If I pass in 'ffflll' then it finds the row which contains lllkkk, just doesn't find it when there's a / in it. What's that about?
ALTER PROCEDURE [dbo].[MyStoredProcedure]
#MyId int,
#MyVarField varchar
AS
BEGIN
SET NOCOUNT ON;
SELECT * FROM MyTable WHERE VarField = #VarField
END
What do I need to do to the stored procedure to make it accept the / as part of a varchar?
Try making your parameter match the same type as the statement (and presumably the underlying column), and giving it a length:
ALTER PROCEDURE [dbo].[MyStoredProcedure]
#MyId int,
#MyVarField NVARCHAR(4000)
AS
...
I don't think the failure currently has anything to do with the slash. Try this:
CREATE PROCEDURE dbo.foo
#bar VARCHAR
AS
PRINT #bar;
GO
EXEC dbo.foo '12345';
For some background, please read:
Bad habits to kick : declaring VARCHAR without (length)
Forward slash (“/” ) is the shortcut for RUN command (like “go” in mysql or sqlserver).
http://power2build.wordpress.com/2011/12/09/forward-slash-in-sqlplus/

nested insert exec work around

I have 2 stored procedures usp_SP1 and usp_SP2. Both of them make use of insert into #tt exec sp_somesp. I wanted to create a 3rd stored procedure which will decide which stored proc to call. Something like:
create proc usp_Decision
(
#value int
)
as
begin
if (#value = 1)
exec usp_SP1 -- this proc already has insert into #tt exec usp_somestoredproc
else
exec usp_SP2 -- this proc too has insert into #tt exec usp_somestoredproc
end
Later, I realized I needed some structure defined for the return value from usp_Decision so that I can populate the SSRS dataset field. So here is what I tried:
Within usp_Decision created a temp table and tried to do "insert into #tt exec usp_SP1". This didn't work out. error "insert exec cannot be nested"
Within usp_Decision tried passing table variable to each of the stored proc and update the table within the stored procs and do "select * from ". That didn't work out as well. Table variable passed as parameter cannot be modified within the stored proc.
Please suggest what can be done.
Can you modify usp_SP1 and usp_SP2?
If so, in usp_Decision, create a local temporary table with the proper schema to insert the results:
create table #results (....)
Then, in the called procedure, test for the existence of this temporary table. If it exists, insert into the temporary table. If not, return the result set as usual. This helps preserve existing behavior, if the nested procedures are called from elsewhere.
if object_id('tempdb..#results') is not null begin
insert #results (....)
select .....
end
else begin
select ....
end
When control returns to the calling procedure, #results will have been populated by the nested proc, whichever one was called.
If the result sets don't share the same schema, you may need to create two temporary tables in usp_Decision.
Have you had a look at table-valued user-defined functions (either inline or multi-statement)? Similar to HLGEM's suggestion, this will return a set which you may not have to insert any where.
Not a fan of global temp tables in any event (other processes can read these table and may interfere with the data in them).
Why not have each proc use a local temp table and select * from that table as the last step.
Then you can insert into a local temp table in the calling proc.
esimple example
create proc usp_mytest1
as
select top 1 id into #test1
from MYDATABASE..MYTABLE (nolock)
select * from #test1
go
--drop table #test
create proc usp_mytest2
as
select top 10 MYTABLE_id into #test2
from MYDATABASE..MYTABLE (nolock)
select * from #test2
go
create proc usp_mytest3 (#myvalue int)
as
create table #test3 (MYTABLE_id int)
if #myvalue = 1
Begin
insert #test3
exec ap2work..usp_mytest1
end
else
begin
insert #test3
exec ap2work..usp_mytest2
end
select * from #test3
go
exec ap2work..usp_mytest3 1
exec ap2work..usp_mytest3 0
See this blog article for one wortkaround (uses OPENROWSET to essentially create a loopback connection on which one of the INSERT EXEC calls happens)

Resources