SQL Server: CREATE FUNCTION with declare variables inside - sql

I would like to create a function in SQL Server.
In this function, I need to define some variables and then use it in the SELECT.
SQL looks like below:
CREATE FUNCTION [dbo].[MyFussnction]
(
#path [nvarchar](10)
)
RETURNS TABLE
BEGIN
DECLARE #xx varchar(50);
SET #xx = 'Windows%';
RETURN
SELECT * FROM MyTable WHERE DataPath LIKE #path AND XX LIKE #xx;
END
But, it is not able to be created and the error says:
Msg 102, Level 15, State 31, Procedure MyFussnction, Line 12 [Batch Start Line 0]
Incorrect syntax near 'BEGIN'.

You need to define columns of table to return, then you can use declare, something like below
CREATE FUNCTION [dbo].[MyFussnction] (
#path [nvarchar](10)
)
RETURNS #Mytable TABLE
(
ID int PRIMARY KEY NOT NULL
-- define other columns
)
AS
BEGIN
DECLARE #xx varchar(50);
SET #xx = 'Windows%';
Insert into #Mytable
SELECT Id FROM MyTable WHERE DataPath LIKE #path AND XX LIKE #xx;
RETURN;
END

Related

transact sql - can't select from variable

I'm trying to take the ID of a new record inserted into TABLE A and use it in a subsequent insert.
But I'm getting an error saying that the newUserId variable isn't declared.
it's actually a table variable.
The code looks like this;
USE Acme;
GO
DECLARE #userPrincipalName VARCHAR(100),
#displayName VARCHAR(100),
#domainName VARCHAR(100),
#tId INT,
#uname VARCHAR(100);
DECLARE #newUserid TABLE (
id INT
);
-- FILL ME IN
SET #domainName = 'mydomain.org';
SET #userPrincipalName = 'ppan#mydomain.org';
SET #displayName = 'Pan, Peter';
SET #tId=4;
SET #uname = 'ppan';
INSERT INTO dbo.User
(column list)
OUTPUT Inserted.ID INTO #newUserId
SELECT '', #domainName, getutcdate(), #userPrincipalName, #displayName, other fields
-- Create New Profile Using NewID ** THIS IS WHERE IT DIES
INSERT INTO dbo.UserProfile
SELECT #newUserId.id,
'{}', GETDATE(), getdate(), ''
The specific error is:
8:55:51 AMStarted executing query at Line 1
Commands completed successfully.
8:55:51 AMStarted executing query at Line 3
Msg 137, Level 16, State 1, Line 36
Must declare the scalar variable "#newUserid".
Total execution time: 00:00:00.017
I've abbreviated the code for the sake of this post but line 36 is where I'm referencing SELECT #newUserId.id
Any tips would be appreciated.
Thanks
Because #newUserId is a table variable you can't select it as a variable.
you can try to use INSERT INTO ....SELECT ... FROM
INSERT INTO dbo.UserProfile
SELECT id, '{}', GETDATE(), GETDATE(), ''
FROM #newUserId

Subquery as parameter into SQL Server UDF

My question is similar to this.
I made a scalar function like follows:
CREATE FUNCTION [dbo].[MyFunction](#table [TableModel] READONLY)
RETURNS DECIMAL(18, 6) AS
BEGIN
DECLARE #sql NVARCHAR(MAX), #params NVARHCAR(MAX), #value DECIMAL(16, 8);
SELECT #sql = formula, #params = params FROM formulas WHERE id = (SELECT TOP 1 id_formula FROM #table)
EXEC sp_executesql #sql, #params, #table=#table, #value=#value OUTPUT;
RETURN #value;
END
Where a formula SQL could be something like:
SELECT #value = SUM(value) / AVG(value) FROM #table
And it could have more columns if needed.
The model table looks like so:
CREATE TYPE [dbo].[TableModel] AS TABLE(
[formula] INT NOT NULL,
[value] DECIMAL(16, 8) NOT NULL
)
And I want to use it like so:
SELECT od.id, od.col1, od.col2,
dbo.MyFunction((SELECT id.formula, id.value FROM #raw_data id WHERE id.id = od.id)) as result
FROM #data od
GROUP BY od.id, od.col1, od.col2
Where the ID unique and multiple rows will have the same id.
Basically, what I'm trying to do in a single query I want to call a function that has a table parameter. But I want this table to be a subquery.
I'm aware that you can call the function with a table variable as mentioned in this answer.
Is this possible in any way? I'm having this error at executing:
Msg 116, Level 16, State 1, Line 30
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.

How do I pass a list as a parameter in a stored procedure?

Looking to pass a list of User IDs to return a list names. I have a plan to handle the outputed names (with a COALESCE something or other) but trying to find the best way to pass in the list of user IDs.
The guts of my sproc will look something like this:
create procedure [dbo].[get_user_names]
#user_id_list, --which would equal a list of incoming ID numbers like (5,44,72,81,126)
#username varchar (30) output
as
select last_name+', '+first_name
from user_mstr
where user_id in #user_id_list
Passing the values for #user_id_list is my main concern here.
The preferred method for passing an array of values to a stored procedure in SQL server is to use table valued parameters.
First you define the type like this:
CREATE TYPE UserList AS TABLE ( UserID INT );
Then you use that type in the stored procedure:
create procedure [dbo].[get_user_names]
#user_id_list UserList READONLY,
#username varchar (30) output
as
select last_name+', '+first_name
from user_mstr
where user_id in (SELECT UserID FROM #user_id_list)
So before you call that stored procedure, you fill a table variable:
DECLARE #UL UserList;
INSERT #UL VALUES (5),(44),(72),(81),(126)
And finally call the SP:
EXEC dbo.get_user_names #UL, #username OUTPUT;
As far as I can tell, there are three main contenders: Table-Valued Parameters, delimited list string, and JSON string.
Since 2016, you can use the built-in STRING_SPLIT if you want the delimited route: https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
That would probably be the easiest/most straightforward/simple approach.
Also since 2016, JSON can be passed as a nvarchar and used with OPENJSON: https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql
That's probably best if you have a more structured data set to pass that may be significantly variable in its schema.
TVPs, it seems, used to be the canonical way to pass more structured parameters, and they are still good if you need that structure, explicitness, and basic value/type checking. They can be a little more cumbersome on the consumer side, though. If you don't have 2016+, this is probably the default/best option.
I think it's a trade off between any of these concrete considerations as well as your preference for being explicit about the structure of your params, meaning even if you have 2016+, you may prefer to explicitly state the type/schema of the parameter rather than pass a string and parse it somehow.
Azure DB, Azure Data WH and from SQL Server 2016, you can use STRING_SPLIT to achieve a similar result to what was described by #sparrow.
Recycling code from #sparrow
WHERE user_id IN (SELECT value FROM STRING_SPLIT( #user_id_list, ',')
Simple and effective way of accepting a list of values into a Stored Procedure
You can try this:
create procedure [dbo].[get_user_names]
#user_id_list varchar(2000), -- You can use any max length
#username varchar (30) output
as
select last_name+', '+first_name
from user_mstr
where user_id in (Select ID from dbo.SplitString( #user_id_list, ',') )
And here is the user defined function for SplitString:
Create FUNCTION [dbo].[SplitString]
(
#Input NVARCHAR(MAX),
#Character CHAR(1)
)
RETURNS #Output TABLE (
Item NVARCHAR(1000)
)
AS
BEGIN
DECLARE #StartIndex INT, #EndIndex INT
SET #StartIndex = 1
IF SUBSTRING(#Input, LEN(#Input) - 1, LEN(#Input)) <> #Character
BEGIN
SET #Input = #Input + #Character
END
WHILE CHARINDEX(#Character, #Input) > 0
BEGIN
SET #EndIndex = CHARINDEX(#Character, #Input)
INSERT INTO #Output(Item)
SELECT SUBSTRING(#Input, #StartIndex, #EndIndex - 1)
SET #Input = SUBSTRING(#Input, #EndIndex + 1, LEN(#Input))
END
RETURN
END
I solved this problem through the following:
In C # I built a String variable.
string userId="";
I put my list's item in this variable. I separated the ','.
for example: in C#
userId= "5,44,72,81,126";
and Send to SQL-Server
SqlParameter param = cmd.Parameters.AddWithValue("#user_id_list",userId);
I Create Separated Function in SQL-server For Convert my Received List (that it's type is NVARCHAR(Max)) to Table.
CREATE FUNCTION dbo.SplitInts
(
#List VARCHAR(MAX),
#Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT Item = CONVERT(INT, Item) FROM
( SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
FROM ( SELECT [XML] = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
WHERE Item IS NOT NULL
);
In the main Store Procedure, using the command below, I use the entry list.
SELECT user_id = Item FROM dbo.SplitInts(#user_id_list, ',');
this is perfect working for me . this perfect example i hope solved many users problem.
Step 1
Creare reference table in sql like this
Create TYPE dbo.tblNames
AS TABLE
(
[Name] nvarchar(max)
);
go
create TYPE dbo.tblNamesWithCols
AS TABLE
(
[Name] nvarchar(max)
);
go
Step 2
create store procedure with reference table parameters like this
create proc syTest
#VarTbleNameList AS dbo.tblNames READONLY,
#VarTbleNameColsList AS dbo.tblNamesWithCols READONLY,
#VarWhereQuery nvarchar(max)
as
begin
......
...... End
**Calling Store Procedure with parameters **
DECLARE #VarTbleList AS dbo.tblNames
INSERT INTO #VarTbleList
VALUES ( 'tblEmployes' )
INSERT INTO #VarTbleList
VALUES ( 'tblDepartments' )
INSERT INTO #VarTbleList
VALUES ( 'tblCities' )
DECLARE #VarTbleColList AS dbo.tblNamesWithCols
INSERT INTO #VarTbleColList
VALUES ( 'tblEmployes.EmployeId as empId;' )
INSERT INTO #VarTbleColList
VALUES ( 'tblEmployes.EmployeName as empName;' )
INSERT INTO #VarTbleColList
VALUES ( 'tblDepartments.DepartmentName as deptName;' )
INSERT INTO #VarTbleColList
VALUES ( 'tblDepartments.DepartmentId as deptId;' )
EXECUTE syTest #VarTbleList , #VarTbleColList , #VarWhereQuery ='test'
You can use this simple 'inline' method to construct a string_list_type parameter (works in SQL Server 2014):
declare #p1 dbo.string_list_type
insert into #p1 values(N'myFirstString')
insert into #p1 values(N'mySecondString')
Example use when executing a stored proc:
exec MyStoredProc #MyParam=#p1
Check the below code this work for me
#ManifestNoList VARCHAR(MAX)
WHERE
(
ManifestNo IN (SELECT value FROM dbo.SplitString(#ManifestNoList, ','))
)
The proper way is to create a user defined data type:
CREATE TYPE [dbo].[IntArray] AS TABLE
(
[ID] [INT] NULL
)
Then you can use this custom data type:
CREATE OR ALTER PROCEDURE [dbo].[sp_GetUserNames]
(
#userIds [IntArray] READONLY
)
AS
BEGIN
SET NOCOUNT ON;
SELECT
"Name" = u.LastName + ', ' + u.FirstName
FROM dbo.User u
JOIN #userIds uid ON u.Id = uid.Id;
END
Usage:
#DECLARE #result TABLE
(
Name NVARCHAR(max)
);
#DECLARE #ids [IntArray] = SELECT x.userId FROM dbo.sometable x;
SET #result = EXECUTE [dbo].[sp_GetUserNames] #userIds = #ids;
SELECT * FROM #result;
Maybe you could use:
select last_name+', '+first_name
from user_mstr
where ',' + #user_id_list + ',' like '%,' + convert(nvarchar, user_id) + ',%'

SQL Error: "Must declare the scalar variable" when passing a table parameter to a table-valued function

The following simple SQL example is returning an error.
Here's a table type that's passed to a table-valued function:
CREATE TYPE Ids
AS TABLE
(
Id int NOT NULL,
PRIMARY KEY( Id )
);
GO
And here is the table-valued function that fails:
CREATE FUNCTION GetIds
(
#ids -- or null
Ids READONLY
)
RETURNS
#result
TABLE
(
EachId int
)
AS
BEGIN
IF #ids IS NOT NULL
INSERT INTO #result
SELECT Id
FROM #ids;
RETURN;
END;
GO
The error returned is:
Msg 137, Level 16, State 1, Procedure GetIds, Line 28
Must declare the scalar variable "#ids".
I've read posts that say that it happens when the SQL compatibility level is too old, but the following returns 100:
SELECT compatibility_level
FROM sys.databases
WHERE name = 'TheDatabaseName';
Any suggestions would be greatly appreciated.
Let me tell you table type parameter is just like a data table.
So, if you want to put if condition on it then,
just change your if condition of function as below:
IF (select count(*) from #ids) > 0
Complete function code is:
CREATE FUNCTION GetIds
(
#ids Ids READONLY
)
RETURNS #result TABLE(EachId int)
AS
BEGIN
IF (select count(*) from #ids) > 0
INSERT INTO #result
SELECT Id FROM #ids;
RETURN;
END;
just check if you have any record in you table
if(select count(1) from #ids)>0

<table-valued function> is not a recognized built-in function name

I am getting this error:
Msg 195, Level 15, State 10, Line 1
'fnParseName' is not a recognized built-in function name.
On this query:
SELECT fnParseName(DOCTORFIRSTNAME+' ' +DOCTORLASTNAME)
FROM [PracticeandPhysician]
Here's the code for fnParseName
create FUNCTION [dbo].[fnParseName]
(#FullName NVARCHAR(128))
RETURNS #FullNameParts TABLE (FirstName NVARCHAR(128),
Middle NVARCHAR(128),
LastName NVARCHAR(128))
AS
BEGIN
... function body that populates #FullNameParts ...
RETURN
END
Why am I getting this error?
It's a table-valued function. So you probably meant:
SELECT p.DOCTORFISTNAME, p.DOCTORLASTNAME, t.FirstName, t.Middle, t.LastName
FROM dbo.[PracticeandPhysician] AS p
CROSS APPLY dbo.fnParseName(p.DOCTORFIRSTNAME + ' ' + p.DOCTORLASTNAME);
Note that you can't say:
SELECT dbo.TableValueFunction('foo');
Any more than you could say:
SELECT dbo.Table;
--or
SELECT dbo.View;
You can, however, say:
SELECT * FROM dbo.fnParseName('foo bar');
--or
SELECT FirstName, Middle, LastName FROM dbo.fnParseName('foo bar');
(Not that I have validated that your function does what you think, or does so efficiently.)
Please always use the dbo. prefix as others have suggested.
You always have to prefix SQL function calls with the schema name dbo. or the schema name for that function (dbo is the default schema).
SELECT dbo.fnParseName(--etc
UDFs/Functions need to be prefixed with the schema name (most likely "dbo"). Change the call to
SELECT
dbo.fnParseName(DOCTORFIRSTNAME + ' ' + DOCTORLASTNAME)
FROM
[PracticeandPhysician]
The problem you have is similar to what I encountered too. Scalar function and Table inline functions are quite different in terms of implementation. See below for the diiferent
Create function udfCountry
(
#CountryName varchar(50)
)
returns varchar(2)
as
BEGIN
Declare #CountryID varchar(2),
#Result varchar(2)
Select #CountryID = Country from
dbo.GeoIPCountryNames where CountryName = #CountryName
set #Result = isNull(#CountryID, 'NA')
if #Result = 'NA'
set #Result = 'SD'
return #Result
End
//Implementation
select dbo.[udfCountry]('Nigeria')
// sample result
NG
// Inline table function sample
Create FUNCTION ConditionEvaluation
(
#CountrySearch varchar(50)
)
returns #CountryTable table
(
Country varchar(2),
CountryName varchar(50)
)
as
Begin
Insert into #CountryTable(Country, CountryName)
Select Country, CountryName from GeoIPCountryNames
where Country like '%'+#CountrySearch+'%'
return
end
//Implementation sample
Declare #CountrySearch varchar(50)
set #CountrySearch='a'
select * from ConditionEvaluation(#CountrySearch)
the parttern of implementating scalar is quite different inline table. I hope this helps
If you want to assign the value returned by tfn in a variable of stored procedure, you can do it this way:
select #my_local_variable_in_procedure = column_name_returned_from_tfn from dbo.my_inline_tfn (#tfn_parameter)