Create a function that accepts list of ids? - sql

I would like to do create a SQL function like this (pseudocode):
function PeopleInCompanies(companyIds)
SELECT * from Person WHERE CompanyId IN (companyIds)
end function
and call it like this:
define companyIds = 1,2,3
select * from PeopleInCompanies(companyIds)
is it even possible?

You would need to use a table type parameter. Assuming that CompanyID is an int:
CREATE TYPE dbo.ints AS TABLE ([value] int);
GO
CREATE FUNCTION dbo.PeopleInCompanies (#CompanyID dbo.ints READONLY)
RETURNS TABLE
AS RETURN
SELECT P.* --This should be replaced by the columns you need.
FROM dbo.Person P
JOIN #CompanyID CI ON P.CompanyID = CI.[Value];
Then you would call the function using:
DECLARE #CompanyID dbo.ints;
INSERT INTO #CompanyID
VALUES (1),(2),(3);
SELECT *
FROM dbo.PeopleInCompanies(#CompanyID);

SQL Server does not support macro substitution. That said, you have the table type as Gordon and Larnu mentioned, or you can simply parse/split the delimited string
Just another option
Declare #companyIds varchar(max) = '1,2,3'
Select A.*
From Person A
Join string_split(#companyIds,',') B
on A.CompanyID = B.Value

Related

Pass Table as parameter to function

I have a temp table which contains data needed in selection, but I can't join in to main select statement because it contains too many rows, and grouping is not good enough. So I decided to use values directly from my temp table in the selection, and it works fine. But since I need to add selection from temp table 50 times as sub queries in that main selection, I was considering to move it to function, as is nicer to call a function 50 times than sub queries.
My question is how to pass values from that table to function? Function will be also supplied with other parameters needed for extraction exact value from that table. I know I can't pass temp table as a parameter, but what about table variable? I don't care if I use temp table or a table variable..
Firstly I wrote a function containing the select statement same as for temp table, and it works but too slow. So if I could pass table results to a function, it will speed up the process..
My function now look like this:
ALTER FUNCTION [document].[GetPersonPremium]
(
-- Add the parameters for the function here
#DocumentId bigint,
#PersonId bigint,
#PeriodId int,
#PersonRole nvarchar(20)
)
RETURNS DECIMAL (18,2)
AS
BEGIN
-- Declare the return variable here
DECLARE #premiumSum decimal (18,2)
-- Add the T-SQL statements to compute the return value here
set #premiumSum =
(select top 1 pt.Premium from document.Document d
inner join document.Person p on p.DocumentCalculationLayerID = dcl.DocumentCalculationLayerID
inner join document.PersonTasks pt on pt.PersonId = p.PersonId
inner join document.PersonCalculationHelper pch on pch.PersonTaskId = pt.PersonTaskId
inner join document.PersonTaskCalculationHelper ptch on ptch.PersonId = p.PersonId
inner join document.PersonMarkTypes pmt on pmt.ConcernMarkTypeID = ptch.ConcernMarkTypeId
where dcl.DocumentID = #DocumentId and p.PersonId = #PersonId and pch.PeriodId = #PeriodId and pmt.Name = #PersonRole)
-- Return the result of the function
RETURN #premiumSum
END
And I would like to use it from stored procedure like this:
...
Engineer = Coalesce(document.GetPersonPremium(#DocumentId, p.PersonID, 65, 'Intern'), 0.00),
...
Any suggestion?
The answer is for those who come to this page with same or similar problem.
I created a table type:
CREATE TYPE PremiumTableType AS TABLE
(
PersonId bigint,
PeriodId int,
PersonRole nvarchar(20),
)
Included it in function:
ALTER FUNCTION [document].[GetPersonPremium]
(
-- Add the parameters for the function here
#DocumentId bigint,
#PersonId bigint,
#PeriodId int,
#PersonRole nvarchar(20),
#PremiumTableType PremiumTableType readonly
)
RETURNS DECIMAL (18,2)
AS
BEGIN
-- Declare the return variable here
DECLARE #premiumSum decimal (18,2)
-- Add the T-SQL statements to compute the return value here
set #premiumSum = (select p.Premium from #PremiumType p where p.PersonId = #PersonId and p.PeriodId = #PeriodId and p.PersonRole = #PersonRole)
-- Return the result of the function
RETURN #premiumSum
END
In SP declared table type variable
Declare #PremiumTableType PremiumTableType
Inserted data from my temp table
Insert into #PremiumTableType (PersonID, PeriodId, ConcernRole, PersonPremium)
Select p.PersonID, ...
And called function from SP like
document.GetPersonPremium(#DocumentID, p.PersonID, pt.PeriodID, 'Intern', #PremiumTableType)

How to declare an array in SQL server query and how to assign value into this array from other select query

ALTER PROCEDURE [dbo].[createTimeFrameReport]
AS
--BEGIN TRAN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--declare #currentYear varchar (4)
--declare #currentMonth varchar(3)
--declare #currentDay varchar(3)
DECLARE #applicationNo varchar(20);
TYPE ListofIDs IS VARRAY(100) OF NUMBER;
//how to assign value for below code a.APPLICATION_ID into an array
SELECT #ListofIDs =a.APPLICATION_ID from BPM_PROCESS_INSTANCE a,BPM_TASK_INSTANCE b,BPM_PROCESS c where b.PROCESS_INSTANCE_ID=a.ID and c.ID=a.TYPE_ID and a.TYPE_ID=42
AND b.ASSIGNED_ROLE IN('IDB_Reviewer','IFP_TechReviewerPermitting','IFP_ProcessManager','IFP_TechReviewerAssessment')
select #ListofIDs
In SQL there is not Array variable, however some SQL features replaces the logic of that array, it depend on how you use it, and i think what you are looking for is Temporary Tables
how to create temporary tables ? , to create temp table you need to have a hashtag sign # before the name of the temp table. see sample below (2 ways to create temp table
Using CREATE TABLE
CREATE TABLE #testTempTable
(
Column1 DataType,
Column2 DataType,
Column3 DataType,
etc...
)
Using SELECT INTO #testTempTable
SELECT Column1, Column2, Column3
INTO #testTempTable
FROM SourceTableNameHere
There is also called Variable Table in SQL , you can google it to know how to use it.
NOTE: it is best practice to drop the temporary table at the end of the script to avoid errors when the script contains temp table runs in the 2nd time.
sytanx:
DROP TABLE #testTempTable
Hope it helps.
SQL Server has not array type but you can use table variables or temp tables instead.
Also please don't use outdated comma syntax, use JOIN ON instead.
TEMP TABLE:
SELECT a.APPLICATION_ID
INTO #ListofIDs
FROM BPM_PROCESS_INSTANCE a
JOIN BPM_TASK_INSTANCE b
ON b.PROCESS_INSTANCE_ID = a.ID
JOIN BPM_PROCESS c
ON c.ID = a.TYPE_ID
WHERE a.TYPE_ID = 42
AND b.ASSIGNED_ROLE IN('IDB_Reviewer',
'IFP_TechReviewerPermitting',
'IFP_ProcessManager',
'IFP_TechReviewerAssessment');
SELECT #ListofIDs;
TABLE VARIABLE:
DECLARE #ListofIDs TABLE
(
APPLICATION_ID int
);
INSERT INTO #ListofIDs(APPLICATION_ID)
SELECT a.APPLICATION_ID
FROM BPM_PROCESS_INSTANCE a
JOIN BPM_TASK_INSTANCE b
ON b.PROCESS_INSTANCE_ID = a.ID
JOIN BPM_PROCESS c
ON c.ID = a.TYPE_ID
WHERE a.TYPE_ID = 42
AND b.ASSIGNED_ROLE IN('IDB_Reviewer',
'IFP_TechReviewerPermitting',
'IFP_ProcessManager',
'IFP_TechReviewerAssessment');
SELECT #ListofIDs;

Combine sp result in select as column

I am trying to execute sp as sub query and treat result set of sp as column of outer query . Some thing like this
Select U.FirstName , (exec SomeSP ) as columnFromSP from User U
Is this possible i searched alot but found nothing on google.
Update
I cannot use #temp table because i am trying to do without #temp table
If you are able to convert your USP to a table value UDF, you will be use the UDF in your FROM statement.
CREATE FUNCTION dbo.SomeUDF
(
-- Add the parameters for the function here
#param varchar(1000)
)
RETURNS TABLE
AS
RETURN
(
SELECT #param as Value
)
GO
SELECT
a.Value,
'B' as Value2
FROM dbo.SomeUDF('ABC') a
Not possible, but you can work around it
Create a temp table & insert the results of the procedure into
it
Now join the User table with the temporary table and select the
columns you want from both tables
This assumes however, you have a joinable expression returned from the stored proc (one that you can match to a field in the user table). If the stored procedure on returns a single row, use a condition of 1=1 or something similar
-- Declare a temp table and column(for eg you have only 1 column)
CREATE TABLE #TEMP
(
FirstName VARCHAR(50)
)
-- The results after execution will be inserted to this table
INSERT INTO #TEMP
Exec SomeSP 'Params'
-- Select records from both tables in all combinations
SELECT U.FirstName , COL1 as columnFromSP
from User U
CROSS JOIN #TEMP

SQL Select Statement including SP Call has syntax error

MYTABLE has ID column. However, following query generates syntax error.
SELECT ID FROM MYTABLE
WHERE ID = EXEC MY_SP ID
What do you think is wrong here?
You can't call stored procedures inline like this.
A couple of options include:
1) Execute the stored procedure and store the results in a temp table. Then use that temp table.
e.g.
CREATE TABLE #Example
(
ID INTEGER
)
INSERT #Example
EXECUTE My_SP
SELECT t.ID FROM MyTable t JOIN #Example e ON t.ID = e.ID
DROP TABLE #Example
2) convert the sproc to a user defined function which you CAN call inline
e.g.
CREATE FUNCTION dbo.MyFunc()
RETURNS TABLE
AS
RETURN
(
SELECT ID FROM SomeTable WHERE ....
)
SELECT t.ID FROM MyTable t JOIN dbo.MyFunc() f ON t.ID = f.ID
3) If the sproc returns a single ID, consider returning an OUTPUT parameter from the sproc instead and use like this:
DECLARE #ID INTEGER
EXECUTE MY_SP #ID OUTPUT
SELECT ID FROM MYTABLE
WHERE ID = #ID
I don't think you need the exec statement, just call the sp, exec is expected to be a separate statement
I don't think you can do that at all. Are you perhaps thinking of a User Defined Function rather than a stored Procedure. Based on the context, you'll need a scalar UDF.
Try this:
SELECT ID FROM MYTABLE WHERE ID = ##SPID

Define variable to use with IN operator (T-SQL)

I have a Transact-SQL query that uses the IN operator. Something like this:
select * from myTable where myColumn in (1,2,3,4)
Is there a way to define a variable to hold the entire list "(1,2,3,4)"? How should I define it?
declare #myList {data type}
set #myList = (1,2,3,4)
select * from myTable where myColumn in #myList
DECLARE #MyList TABLE (Value INT)
INSERT INTO #MyList VALUES (1)
INSERT INTO #MyList VALUES (2)
INSERT INTO #MyList VALUES (3)
INSERT INTO #MyList VALUES (4)
SELECT *
FROM MyTable
WHERE MyColumn IN (SELECT Value FROM #MyList)
DECLARE #mylist TABLE (Id int)
INSERT INTO #mylist
SELECT id FROM (VALUES (1),(2),(3),(4),(5)) AS tbl(id)
SELECT * FROM Mytable WHERE theColumn IN (select id from #mylist)
There are two ways to tackle dynamic csv lists for TSQL queries:
1) Using an inner select
SELECT * FROM myTable WHERE myColumn in (SELECT id FROM myIdTable WHERE id > 10)
2) Using dynamically concatenated TSQL
DECLARE #sql varchar(max)
declare #list varchar(256)
select #list = '1,2,3'
SELECT #sql = 'SELECT * FROM myTable WHERE myColumn in (' + #list + ')'
exec sp_executeSQL #sql
3) A possible third option is table variables. If you have SQl Server 2005 you can use a table variable. If your on Sql Server 2008 you can even pass whole table variables in as a parameter to stored procedures and use it in a join or as a subselect in the IN clause.
DECLARE #list TABLE (Id INT)
INSERT INTO #list(Id)
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
SELECT
*
FROM
myTable
JOIN #list l ON myTable.myColumn = l.Id
SELECT
*
FROM
myTable
WHERE
myColumn IN (SELECT Id FROM #list)
Use a function like this:
CREATE function [dbo].[list_to_table] (#list varchar(4000))
returns #tab table (item varchar(100))
begin
if CHARINDEX(',',#list) = 0 or CHARINDEX(',',#list) is null
begin
insert into #tab (item) values (#list);
return;
end
declare #c_pos int;
declare #n_pos int;
declare #l_pos int;
set #c_pos = 0;
set #n_pos = CHARINDEX(',',#list,#c_pos);
while #n_pos > 0
begin
insert into #tab (item) values (SUBSTRING(#list,#c_pos+1,#n_pos - #c_pos-1));
set #c_pos = #n_pos;
set #l_pos = #n_pos;
set #n_pos = CHARINDEX(',',#list,#c_pos+1);
end;
insert into #tab (item) values (SUBSTRING(#list,#l_pos+1,4000));
return;
end;
Instead of using like, you make an inner join with the table returned by the function:
select * from table_1 where id in ('a','b','c')
becomes
select * from table_1 a inner join [dbo].[list_to_table] ('a,b,c') b on (a.id = b.item)
In an unindexed 1M record table the second version took about half the time...
I know this is old now but TSQL => 2016, you can use STRING_SPLIT:
DECLARE #InList varchar(255) = 'This;Is;My;List';
WITH InList (Item) AS (
SELECT value FROM STRING_SPLIT(#InList, ';')
)
SELECT *
FROM [Table]
WHERE [Item] IN (SELECT Tag FROM InList)
Starting with SQL2017 you can use STRING_SPLIT and do this:
declare #myList nvarchar(MAX)
set #myList = '1,2,3,4'
select * from myTable where myColumn in (select value from STRING_SPLIT(#myList,','))
DECLARE #myList TABLE (Id BIGINT) INSERT INTO #myList(Id) VALUES (1),(2),(3),(4);
select * from myTable where myColumn in(select Id from #myList)
Please note that for long list or production systems it's not recommended to use this way as it may be much more slower than simple INoperator like someColumnName in (1,2,3,4) (tested using 8000+ items list)
slight improvement on #LukeH, there is no need to repeat the "INSERT INTO":
and #realPT's answer - no need to have the SELECT:
DECLARE #MyList TABLE (Value INT)
INSERT INTO #MyList VALUES (1),(2),(3),(4)
SELECT * FROM MyTable
WHERE MyColumn IN (SELECT Value FROM #MyList)
No, there is no such type. But there are some choices:
Dynamically generated queries (sp_executesql)
Temporary tables
Table-type variables (closest thing that there is to a list)
Create an XML string and then convert it to a table with the XML functions (really awkward and roundabout, unless you have an XML to start with)
None of these are really elegant, but that's the best there is.
If you want to do this without using a second table, you can do a LIKE comparison with a CAST:
DECLARE #myList varchar(15)
SET #myList = ',1,2,3,4,'
SELECT *
FROM myTable
WHERE #myList LIKE '%,' + CAST(myColumn AS varchar(15)) + ',%'
If the field you're comparing is already a string then you won't need to CAST.
Surrounding both the column match and each unique value in commas will ensure an exact match. Otherwise, a value of 1 would be found in a list containing ',4,2,15,'
As no one mentioned it before, starting from Sql Server 2016 you can also use json arrays and OPENJSON (Transact-SQL):
declare #filter nvarchar(max) = '[1,2]'
select *
from dbo.Test as t
where
exists (select * from openjson(#filter) as tt where tt.[value] = t.id)
You can test it in
sql fiddle demo
You can also cover more complicated cases with json easier - see Search list of values and range in SQL using WHERE IN clause with SQL variable?
This one uses PATINDEX to match ids from a table to a non-digit delimited integer list.
-- Given a string #myList containing character delimited integers
-- (supports any non digit delimiter)
DECLARE #myList VARCHAR(MAX) = '1,2,3,4,42'
SELECT * FROM [MyTable]
WHERE
-- When the Id is at the leftmost position
-- (nothing to its left and anything to its right after a non digit char)
PATINDEX(CAST([Id] AS VARCHAR)+'[^0-9]%', #myList)>0
OR
-- When the Id is at the rightmost position
-- (anything to its left before a non digit char and nothing to its right)
PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR), #myList)>0
OR
-- When the Id is between two delimiters
-- (anything to its left and right after two non digit chars)
PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR)+'[^0-9]%', #myList)>0
OR
-- When the Id is equal to the list
-- (if there is only one Id in the list)
CAST([Id] AS VARCHAR)=#myList
Notes:
when casting as varchar and not specifying byte size in parentheses the default length is 30
% (wildcard) will match any string of zero or more characters
^ (wildcard) not to match
[^0-9] will match any non digit character
PATINDEX is an SQL standard function that returns the position of a pattern in a string
DECLARE #StatusList varchar(MAX);
SET #StatusList='1,2,3,4';
DECLARE #Status SYS_INTEGERS;
INSERT INTO #Status
SELECT Value
FROM dbo.SYS_SPLITTOINTEGERS_FN(#StatusList, ',');
SELECT Value From #Status;
Most of these seem to focus on separating-out each INT into its own parenthetical, for example:
(1),(2),(3), and so on...
That isn't always convenient. Especially since, many times, you already start with a comma-separated list, for example:
(1,2,3,...) and so on...
In these situations, you may care to do something more like this:
DECLARE #ListOfIds TABLE (DocumentId INT);
INSERT INTO #ListOfIds
SELECT Id FROM [dbo].[Document] WHERE Id IN (206,235,255,257,267,365)
SELECT * FROM #ListOfIds
I like this method because, more often than not, I am trying to work with IDs that should already exist in a table.
My experience with a commonly proposed technique offered here,
SELECT * FROM Mytable WHERE myColumn IN (select id from #mylist)
is that it induces a major performance degradation if the primary data table (Mytable) includes a very large number of records. Presumably, that is because the IN operator’s list-subquery is re-executed for every record in the data table.
I’m not seeing any offered solution here that provides the same functional result by avoiding the IN operator entirely. The general problem isn’t a need for a parameterized IN operation, it’s a need for a parameterized inclusion constraint. My favored technique for that is to implement it using an (inner) join:
DECLARE #myList varchar(50) /* BEWARE: if too small, no error, just missing data! */
SET #myList = '1,2,3,4'
SELECT *
FROM myTable
JOIN STRING_SPLIT(#myList,',') MyList_Tbl
ON myColumn = MyList_Tbl.Value
It is so much faster because the generation of the constraint-list table (MyList_Tbl) is executed only once for the entire query execution. Typically, for large data sets, this technique executes at least five times faster than the functionally equivalent parameterized IN operator solutions, like those offered here.
I think you'll have to declare a string and then execute that SQL string.
Have a look at sp_executeSQL