I would like to build a very complicated Table_valued function (TVF) that will return non fixed output structure.
sometimes the TVF may return 2 columns and other times may return only 1 column.
I couldn't find a way to do this because the database engine requires explicit output table structure as:
RETURNS #returnTable
TABLE
(
column1 numeric,
column2 numeric
)
Once i find a solution for the above i would like to do something like:
SELECT
*
INTO #tmp
FROM MyTVF
I know its possible to implement that with stored procedure but then i will face to another problem. By using stored procedure i will not be able to save the result to a temp table without declaring the output explicitly.
Here is a shot example of what i would like to do:
CREATE FUNCTION [dbo].myFunction (#type int)
RETURNS #table TABLE
(
Column1 int,
Column2 int
)
AS
BEGIN
IF #type=1
BEGIN
INSERT INTO #table
SELECT 1 AS Column1, 2 AS Column2
END
ELSE
BEGIN
INSERT INTO #table
SELECT 1 AS OnlyOneColumn
END
RETURN
END
GO
SELECT * INTO #tmp1 FROM myFunction(1)
SELECT * FROM #tmp1
All Table-Valued Functions return a table with a fixed structure.
However (unlike Multi-Statement Table-Valued Functions), you don't have to declare that structure for an In-Line Table-Valued Function, for example:
CREATE FUNCTION MyFunction(#MyParameter INT)
RETURNS TABLE AS RETURN
SELECT * FROM SomeTable
WHERE SomeColumn=#MyParameter
This function will still return a fixed number of columns: even if you later add columns to SomeTable, they will not be returned by MyFunction, unless the function is modified or refreshed (with sp_refreshsqlmodule).
You cannot create a Table-Valued Function that returns a variable number of columns (depending on the input parameters).
Related
I want to create a stored procedure or database function to delete some entries and return their IDs to the client. I started off with this FUNCTION:
CREATE FUNCTION dbo.FixHangingCarriers ()
RETURNS #returntable TABLE
(
ID_Plant INT NOT NULL,
ID_ChargeCarrier INT NOT NULL
)
AS
BEGIN;
-- Declare a temporary table
DECLARE #EntriesToDelete TABLE (
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NOT NULL,
StoredOn DATETIME2(2) NOT NULL
);
-- Select all the entries that should be deleted
INSERT INTO #EntriesToDelete
SELECT
ID_ChargeCarrier,
ID_Storage,
StoredOn
FROM dbo.CurrentStorage
WHERE ID_StorageType = 4
AND StoredOn < DATEADD(MINUTE, -30, GetDate());
-- Return immediately, if there is currently nothing to delete
IF (SELECT 1 FROM #EntriesToDelete) = 1
RETURN;
-- Delete hanging entries
DELETE ccs
FROM dbo.ChargeCarrier_Storage ccs
INNER JOIN #EntriesToDelete d ON
d.ID_ChargeCarrier = ccs.ID_ChargeCarrier AND
d.ID_Storage = ccs.ID_Storage AND
d.StoredOn = ccs.StoredOn;
-- Prepare return table
INSERT INTO #returntable
SELECT cs.ID
FROM #EntriesToDelete d
INNER JOIN dbo.ChargeCarriersCurrentlyInPlant cp ON
cp.ID_ChargeCarrier = d.ID_ChargeCarrier;
-- Return deleted entries to caller
RETURN;
END;
Obviously this doesn't work, because SQL functions cannot delete anything. I wanted to change it into a PROCEDURE, but then I saw, that procedures naturally seem not to be able to return a table. How can I solve this issue?
As the comments suggest, this seems like all you need is this:
CREATE PROC dbo.FixHangingCarriers
AS BEGIN
DELETE ccs
OUTPUT deleted.ID
FROM dbo.ChargeCarrier_Storage ccs
INNER JOIN CurrentStorage cs ON cs.ID_ChargeCarrier = ccs.ID_ChargeCarrier
AND cs.ID_Storage = ccs.ID_Storage
AND cs.StoredOn = ccs.StoredOn
WHERE cs.ID_StorageType = 4
AND cs.StoredOn < DATEADD(MINUTE, -30, GETDATE());
END;
You basically have two choices.
One is to return the rows as part of a SELECT statement as suggested. However, if you want to consume those in the next bit of T-SQL, you can't just SELECT from a table returned by a stored procedure.
Lots of people ask for a command like SELECT * FROM (EXEC someproc) but that doesn't exist.
What you can do instead is to define a table variable like this:
DECLARE #OutputIDs TABLE
(
OutputIDKey int IDENTITY(1,1) PRIMARY KEY,
OutputID int
);
Then you can use an INSERT EXEC to get the values:
INSERT #OutputIDs (OutputID)
EXEC dbo.MyStoredProcedure;
And then you have the IDs in the table variable to do what you want with. (Note: you could also do that with a temporary table but it isn't as good an option)
The other option is to use an OUTPUT parameter to the stored procedure. However, you'd need to pack all your IDs into a string (perhaps a comma-delimited list) before you return it. You then use a function to unpack the string back to a set of IDs. (If you're on SQL Server 2016 or later, you could use the STRING_SPLIT function).
The trick with using OUTPUT parameters is that you have to define it as OUTPUT when you are calling the procedure, and in the header of the procedure itself. Here's the skeleton code:
CREATE PROCEDURE dbo.DoSomething
#OtherParameter int,
#IDsToOutput nvarchar(max) OUTPUT,
#YetAnotherParameter int
AS
BEGIN
...
END;
And then when you call the procedure, you do this:
DECLARE #OutputIDs nvarchar(max);
EXEC dbo.DoSomething #OtherParameter = 1,
#IDsToOutput = #OutputIDs OUTPUT,
#YetAnotherParameter = 2;
SELECT * FROM STRING_SPLIT(#OutputIDs, ',');
Hope that helps.
I know that some similar questions have been asked before but none of the answers worked for me!
I use POSTGRESQL 8.4 and am trying to return an array of BIGINT values from a function.
My query looks like:
CREATE OR REPLACE FUNCTION public.bigint_func(
in "in_arg" BIGINT)
RETURNS SETOF BIGINT
AS
$body$
DECLARE bigint_list BIGINT [ ];
BEGIN
SELECT
id
FROM
table1
INTO bigint_list;
RETURN NEXT;
END
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
and I'd like to use that function as below:
SELECT *
FROM
table1
JOIN (SELECT ids
FROM bigint_func(123))t2 ON table1.id = t2.id
but I get the following error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
How should I write the code for the function?
Looks like you might be blending two ways of doing what you are trying to accomplish. Either loop through and return a value at a time, or return everything as a query.
I think either of these will work.
Loop and return a row at a time.
CREATE OR REPLACE FUNCTION bigint_func(
in "in_arg" BIGINT)
RETURNS SETOF BIGINT
AS
$body$
DECLARE
bigint_list BIGINT [ ];
my_id bigint;
BEGIN
for my_id in
SELECT
id
FROM
table1
loop
RETURN NEXT my_id;
END loop;
end;
$body$
LANGUAGE 'plpgsql'
VOLATILE
Or return the entire query as a dataset.
CREATE OR REPLACE FUNCTION bigint_func(
in "in_arg" BIGINT)
RETURNS SETOF BIGINT
AS
$body$
DECLARE
bigint_list BIGINT [ ];
BEGIN
return query
SELECT
id
FROM
table1;
end;
$body$
LANGUAGE 'plpgsql'
VOLATILE
-- EDIT --
To get the query working, maybe change the return type to a table:
CREATE OR REPLACE FUNCTION bigint_func(in "in_arg" BIGINT)
RETURNS table (id bigint)
AS
$body$
BEGIN
return query
SELECT
table1.id
FROM
table1;
end;
$body$
LANGUAGE 'plpgsql'
VOLATILE;
Then you can name the field anything you want (id in this case)
SELECT *
FROM
table1
join bigint_func(123) t2
ON table1.id = t2.id
I have a user-defined table type:
CREATE TYPE [dbo].[EntityKeysTable] AS TABLE
(
[EntityKey] [uniqueidentifier] NOT NULL,
PRIMARY KEY CLUSTERED( [EntityKey] ASC) WITH (IGNORE_DUP_KEY = OFF)
)
And a function that uses it... But that function is being used by different processes in our system and I don't want to go in and have to deal with the new parameter everywhere, so I rather just make it optional by having a default value in it...
ALTER FUNCTION [dbo].[fnMyFunctionNameHere]
(#siteId UNIQUEIDENTIFIER,
#entityKeys AS EntityKeysTable READONLY,
#partialDeploy AS BIT = false)
RETURNS TABLE
AS
RETURN (
WITH Overrides AS (
SELECT * ....
I did it for the #partialDeploy but unsure about how to do the same for the #entityKeys.
I am sorry for my prior misleading information.
Table valued functions cannot be null. But you can define them with no records in it. So, you would check for (select count(*) from #entityKeys) = 0 to check if there are entityKeys or not.
if (select count(*) from #entityKeys) = 0
--load #entityKeys as your wish
Some time ago I also searched for the answer to this question, but found nothing. So I did such:
create PROCEDURE dbo.Test
#table dbo.testType readonly
AS
BEGIN
declare #table2 dbo.testType
if(not exists(select * from #table))
.... do something like
insert into #table2(t1,t2) values ....
else
insert into #table2(t1,t2) select t1,t2 from #table
... do main code using #table2
END
User defined table types is optional. So you may not pass it to function.
This what I have so far individually all for sections work fine, I just cant figure out how to combine them properly.
Table function and procedure
USE [MENUdb]
GO
CREATE Function [dbo].[func_GetMenuItemsForMenu]
(
#MENUID NVARCHAR (50)
)
RETURNS TABLE
AS
RETURN
(
SELECT MenuItem.MenuID,MenuItem.MenuItemID,MenuItem.MenuItemTitle FROM MenuItem,Menu
WHERE Menu.MenuID = #MENUID
AND MenuItem.MenuID = Menu.MenuID)
GO
USE [MENUdb]
GO
ALTER Procedure [dbo].[sp_GetMenuItemsForMenu]
(
#ENTER_MENUID int
)
AS
BEGIN
SELECT * FROM func_GetMenuItemsForMenu (#ENTER_MENUID)
END
Scalar function and procedure
USE [MENUdb]
ALTER FUNCTION [dbo].[func_GetMenuItemDescriptionForMenuItemID]
(
#MENUITEMID int
)
RETURNS nvarchar(MAX)
AS
BEGIN
RETURN
(
SELECT MenuItem.MenuItemTitle +' ' + MenuItem.MenuItemDescriptionText FROM MenuItem WHERE MenuItemID = #MENUITEMID
)
END
USE [MENUdb]
GO
ALTER Procedure [dbo].[sp_GetMenuItemDescriptionForMenuItemID]
(
#ENTER_MENUITEMID int
)
AS
BEGIN
SELECT dbo.func_GetMenuItemDescriptionForMenuItemID (#ENTER_MENUITEMID)
END
Individually they work fine exactly as they should besides the fact that I am not sure how to output if there are multiple rows that fit the input for the scalar
My main issue is that I can quite figure out how to get the result of the Table function specifically the MenuItemID column to work as the input of the Scalar function
what should happen is this, say there are 3 items on menu 1, I enter 1 in the initial execution and it returns the 3 items IDs and Names in table form, how do I pass the IDs in the MenuItemID column to the next function?
Coding is not my strong point so im probably missing something very simple...
I Have a UDF that returns a TABLE.
In my SP i define a temp table variable:
declare #t table ([ID] uniqueidentifier)
How do I set #t = MyDF?
Only method that works is:
INSERT INTO #t SELECT * FROM dbo.MyUDF(#List)
This does not work:
SELECT * INTO #t FROM dbo.MyUDF(#List) -- <- This is just a note, and NOT the question
My Question: Can't I just somehow assign select #t = dbo.MyUDF?
Is there any other way to directlly assign the UDF result TABLE to the temp variable other than actually "INSERT INTO"/"SELECT INTO"
EDIT: I know I can reference dbo.MyUDF() in my query directly without using a temp table variable. but since I need to reference this a few times in my SP, I prefer to work with a local variable which will be assigned only once, and not process dbo.MyUDF() each time I reference it.
My UDF is very much the same as in this answer: https://stackoverflow.com/a/11105413/3933402 (SplitInts - only mine returns uniqueidentifier)
could you try
SELECT * INTO #t FROM dbo.MyUDF(#List)