Reputation: 87
I am working with SQL Server 2008.
I have a custom function called fn_takeTableArg and I need to be able to use it like this:
select dbo.fn_takeTableArg(<a union of multiple selects>) as MyField from MyTable
The custom table arg is:
CREATE TYPE [dbo].[TableArg] AS TABLE(
Value VARCHAR(2000),
RepOrApp CHAR
SortOrder INT
)
Function is something like:
CREATE FUNCTION dbo.fn_takeTableArg
(
@t [dbo].[TableArg] READONLY
)
RETURNS VARCHAR(max)
AS
... Do something over the passed in table with a with clause, etc.
But, seems like this can't be done as I get the "Only one expression can be specified in the select list when the subquery is not introduced with EXISTS." error.
Presumably because I am expected to create a temp table variable first and then call my function with that variable?
But, I can't do that because I have to call this function from inside another select.
Upvotes: 2
Views: 2252
Reputation: 67311
I really doubt, that your approach is the best you can choose, but to answer your question: You have to fill a typed parameter like any other table first and pass it in:
USE master;
GO
CREATE DATABASE testdb;
GO
USE testdb;
GO
CREATE TYPE [dbo].[TableArg] AS TABLE(
[Value] VARCHAR(2000)
,RepOrApp CHAR
,SortOrder INT
);
GO
CREATE FUNCTION dbo.fn_takeTableArg
(
@t [dbo].[TableArg] READONLY
)
RETURNS VARCHAR(max)
AS
BEGIN
RETURN (SELECT TOP 1 [Value] FROM @t);
END
GO
--declare the parameter with your type
DECLARE @TheParameter dbo.TableArg;
--... and fill it
INSERT INTO @TheParameter([Value],RepOrApp,SortOrder)
SELECT o.[name],'x',1
FROM sys.objects o;
--The parameter is like a table
SELECT * FROM @TheParameter;
--...and can be passed as the funcion's argument
SELECT dbo.fn_takeTableArg(@TheParameter);
GO
USE master;
GO
DROP DATABASE testdb;
Just saw your remark
Presumably because I am expected to create a temp table variable first and then call my function with that variable?
But, I can't do that because I have to call this function from inside another select.
There is a trick for this: You can wrap a SELECT ... FOR XML
in paranthesis and handle the returned XML similar to a scalar value.
CREATE FUNCTION dbo.fn_takeTableArg
(
@t XML --You function accepts an XML
)
RETURNS VARCHAR(max)
AS
BEGIN
--do something with the XML, in this case: return the first attribute you find
RETURN @t.value(N'(//@*)[1]','varchar(max)');
END
GO
--The function can take the generic `SELECT` as argument
--(not really, but the full result-set translated to an XML)
SELECT dbo.fn_takeTableArg(
(
SELECT [name]
,'x' AS RepOrApp
,1 AS SortOrder
FROM sys.objects
FOR XML AUTO)
);
GO
Within the function the XML looks like this:
<sys.objects name="sysrscols" RepOrApp="x" SortOrder="1" />
<sys.objects name="sysrowsets" RepOrApp="x" SortOrder="1" />
<sys.objects name="sysclones" RepOrApp="x" SortOrder="1" />
...
All namings ('sys.objects', 'name' etc.) can be controlled with the aliases you choose.
Scalar functions are bad in performance. A scalar function dealing with an XML, which has to be created for each and any row will be very slow... There are much better approaches assumably...
Upvotes: 1
Reputation: 50163
You can pass value in scalar function
as
select dbo.fn_takeTableArg(t.col) as MyField
from (<a union of multiple selects>) t;
Upvotes: 0