INPUT a List to stored procedure - sql

I get a list of elments from R and I have to obtain the records from database that belong to a the list of elements.
INPUT:
'12345','23456', '34567', '45678'
PROCEDURE:
CREATE PROCEDURE "SCHEMA"."GET_RECORDS" (IN LIST (Type), OUT RECORDS tt_records)
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
READS SQL DATA AS
BEGIN
RECORDS = select * from TABLE where ids in :LIST
END;
How can I provide such a list to the proceudre?

Handing over lists of parameters to SQLScript is a bit tricky as there is no straight-forward native construct for that.
One way to do it is to use the APPLY_FILTER function and to "smuggle" the list as a string parameter.
In my example I read from a table CUSERS and I create a filter condition for APPLY_FILTER that filters column USER_ID via an IN ( ) clause.
Removing the single quotes (' ') from the list is to avoid implicit type conversion when executing the query. Leaving the single quotes in place would make the IN () clause make look like this:
IN ( '<1st value>', '<2nd value>', '<3rd value>', ...)
instead of
IN (<1st value>, <2nd value>, <3rd value>, ...).
CREATE PROCEDURE "GET_RECORDS" (IN id_list VARCHAR(4000)
, OUT RECORDS tt_cusers)
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
READS SQL DATA AS
BEGIN
declare _filter VARCHAR(4000);
_users = select * from cusers;
-- APPLY_FILTER expects a proper WHERE condition, so adding the column to filter
-- and the IN () expression is necessary.
--
-- the the id_list comes in with single quotes, let's remove those
_filter = 'USER_ID in (' || replace (:id_list, '''', '') ||')';
RECORDS = APPLY_FILTER(:_users, :_filter);
end;
call get_records (?, ?)
-- this 'list' is to be used as a single parameter value
-- '131072', '161223', '131074'
A slightly more comfortable approach for getting the data out from SAP HANA into R can be using a table typed user-defined function (UDF) instead. The main difference here is that the calling statement is a simple SELECT and the result is simply the resultset of this SELECT.
CREATE function "FGET_RECORDS" (IN id_list VARCHAR(4000))
returns tt_cusers
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
READS SQL DATA AS
BEGIN
declare _filter VARCHAR(4000);
_users = select * from cusers;
-- APPLY_FILTER expects a proper WHERE condition, so adding the column to filter
-- and the IN () expression is necessary.
--
-- the the id_list comes in with single quotes, let's remove those
_filter = 'USER_ID in (' || replace (:id_list, '''', '') ||')';
_result = APPLY_FILTER(:_users, :_filter);
RETURN :_result;
end;
select * from fget_records (? );
In R (or in any other client) make sure to use bind variables when using this construct. Otherwise handling the different string quote-mechanisms can become cumbersome.
See the documentation on APPLY_FILTER here.

Use a User Defined Data Type.
First Create A User Defined Data Type
Database Node > Programmability > Types > User-Defined Table Types
Script :
CREATE TYPE dbo.MyTableType AS TABLE
(
ID INT
)
Create a Parameter in your procedure with the above type
CREATE PROCEDURE usp_InsertMessages
(
#MyParameter MyTableType READONLY
)
AS
BEGIN
INSERT INTO MyTable
(
id
)
SELECT
id
FROM #MyParameter
END

Related

How do I pass multiple entries through an input parameter mapped from a Table Function in SAP HANA

How do I pass multiple entries through an input parameter mapped from a Table Function in SAP HANA ?
I've written a Table Function with an Input Parameter say IN_FORMAT_CD.
I've mapped this parameter to the one created in my calculation view.
I'm able to retrieve the data when I'm passing only one value say 100.
But it gives no result when I'm passing more than one value.
Is there any workaround for the same ?
My table function :
FUNCTION "HADMIN"."RA.Test.Prathamesh::PH_DEMO" (IN IN_FORMAT_CD NVARCHAR(500))
RETURNS TABLE (NAME NVARCHAR(10), ID NVARCHAR(10), FORMAT_CD NVARCHAR(3))
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER AS
BEGIN
RETURN
SELECT NAME,ID,FORMAT_CD
FROM
HADMIN.PH_DEMO
WHERE FORMAT_CD IN (select :IN_FORMAT_CD as FORMAT_CD from dummy);
END;
What you are looking for is the APPLY_FILTER function of SAP HANA SQLScript.
The following example shows how your scenario could be coded:
create function get_vals (IN id_list varchar(400))
returns table (id bigint, val varchar(40))
as
begin
declare myfilter varchar(450) := ' ID in (' || :id_list || ')';
_tmp = select id, val from some_vals;
_tmp2 = APPLY_FILTER (:_tmp, :myfilter);
return :_tmp2;
end;
select *
from
get_vals ('1, 4, 23, 4, 23, 3');
This approach will push down the unique list of IDs to be used as a filter when reading the table data. However, this is still dynamic SQL so you lose benefits like plan sharing and risk SQL injection attacks. Read more on this e.g. here.
If possible, you want to handle selection lists in your application code.
This, in turn, would also give you the option to decide whether using IN-lists or inner joins against temporary tables is the best approach for your situation.
In case you want to go with the selection list as a string, you should at least make sure, that common SQL injection attacks are not used and that the "in-list" really only contains possible ID values and commas.
it is not possible to produce(!) many items from a single sql variable unless you split them
In your SQL subselect query will return only rows that FORMAT_CD column values are exactly same with IN_FORMAT_CD parameter.
If this parameter represents more than one value, then this parameter is a concatenated string representation of each sub items. So we can split them back.
Splitting will produce a table on the fly which can be used for selection.
Please create the user-defined HANA Split function fnsplit that source codes can be found at referenced document
Then you can alter your function as follows assuming that each value is seperated with "," from others
ALTER FUNCTION "HADMIN"."RA.Test.Prathamesh::PH_DEMO" (IN IN_FORMAT_CD NVARCHAR(500))
RETURNS TABLE (NAME NVARCHAR(10), ID NVARCHAR(10), FORMAT_CD NVARCHAR(3))
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER AS
BEGIN
RETURN
SELECT NAME,ID,FORMAT_CD
FROM
HADMIN.PH_DEMO
WHERE FORMAT_CD IN (
select str from fnsplit(:IN_FORMAT_CD,',')
);
END;

Multiple values in a single parameter of a scalar function

Is there a way to input multiple values in a single parameter of a scalar-valued function in SQL Server 2008 R2 and have it filter data by that parameter using both values?
For example I would like to do the following
SET #Salesperson='BILL' OR 'MOSES'
SELECT Sum(SalesDollars)
FROM Invoices
WHERE Invoices.Salesperson = #Salesperson
I attempted to use the following as the WHERE clause, but this didnt work either.
SET #Salesperson='BILL','MOSES'
SELECT Sum(SalesDollars)
FROM Invoices
WHERE Invoices.Salesperson IN (#Salesperson)
Would it be easier if i were dealing with integers as opposed to varchar values?
Any help would be absolutely appreciated!
You need to use table-valued parameters. Look it up on technet or msdn
Best part of it that your table-valued parameters can have multiple columns.
Note however that you have to define TVP parameter as readonly. So if you want to return similar set from your function you will need to create another variable inside your function.
Example:
CREATE TYPE Names AS TABLE
( Name VARCHAR(50));
GO
/* Create a procedure to receive data for the table-valued parameter. */
CREATE PROCEDURE dbo.mySP
#n Names READONLY
AS
SELECT Sum(SalesDollars)
FROM
WHERE Invoices.Salesperson in (select Name from #n)
GO
CREATE FUNCTION dbo.myFun(#n Names READONLY) returns int
AS
SELECT Sum(SalesDollars)
FROM
WHERE Invoices.Salesperson in (select Name from #n)
GO
/* Declare a variable that references the type. */
DECLARE #names AS Names;
/* Add data to the table variable. */
INSERT INTO #names (Name)
VALUES ('BILL'),('MOSES')
-- using stored procedure with TVP
EXEC dbo.mySP #names
-- using function with TVP
select dbo.myFun(#names)
GO
This could be done this way:
SET #Salesperson='BILL,MOSES'
SELECT *
FROM YourTable
WHERE Invoices.Salesperson IN (SELECT * FROM dbo.split(#Salesperson,','))
This is how you split the values.
I would typically do this using a user defined table type: SQL Fiddle Example.
CREATE TYPE <schema>.SalespersonList AS TABLE
(
Name varchar(32)
)
You may have to grant execute permissions on the type:
GRANT EXECUTE ON TYPE::<schema>.SalespersonList TO <user>
Then you can create a function to use it:
CREATE FUNCTION <schema>.fnGetTotalSales
(
#nameList <schema>.SalespersonList READONLY
)
RETURNS INT
AS
BEGIN
DECLARE #ret INT
SELECT #ret = Sum(SalesDollars)
FROM Invoices i
INNER JOIN #nameList nl ON nl.Name = i.Salesperson
RETURN #ret
END
Then you would just insert your list into the type and call the function:
DECLARE #salesPersonList <schema>.SalespersonList
INSERT INTO #salesPersonList (Name)
SELECT 'Bill'
UNION
SELECT 'Moses'
SELECT <schema>.fnGetTotalSales(#salesPersonList)

Returning a table from a function with parameters in SQL

As far as I know, we can return a table as a result of a db function:
CREATE FUNCTION MyFunction(#Value varchar(100))
RETURNS table
AS RETURN (select * from MyTable where ColumnName = '#Value')
in this example we can make the column name as a parameter for the function. My question is, can we write the column name and table name as a parameter for the function? hence we can write a more generic function something like:
CREATE FUNCTION MyGenericSearchFunction(#TableName varchar(100), #ColumnName varchar(100), #Value varchar(100))
RETURNS table
AS RETURN (select * from #TableName where #ColumnName = '#Value')
No, you can't.
This would then be a dynamic query.
For dynamic queries in SQL Server, one has to use exec() or sp_executesql() functions, which are not allowed in functions.

How to concatenate variables into SQL strings

I need to concatenate a variable table name into my SQL query such as the following...
ALTER FUNCTION fn_myfunction(#KeyValue text)
BEGIN
INSERT INTO #tmpTbl1
SELECT #KeyValue AS fld1
FROM tbl + #KeyValue + KeyValue.fld1
I also attempted the following but it told me I had to declare the table variable?
ALTER FUNCTION fn_myfunction(#KeyValue text, #KeyTable text)
FROM #KeyTable.fld1
You can accomplish this (if I understand what you are trying to do) using dynamic SQL.
The trick is that you need to create a string containing the SQL statement. That's because the tablename has to specified in the actual SQL text, when you execute the statement. The table references and column references can't be supplied as parameters, those have to appear in the SQL text.
So you can use something like this approach:
SET #stmt = 'INSERT INTO #tmpTbl1 SELECT ' + #KeyValue
+ ' AS fld1 FROM tbl' + #KeyValue
EXEC (#stmt)
First, we create a SQL statement as a string. Given a #KeyValue of 'Foo', that would create a string containing:
'INSERT INTO #tmpTbl1 SELECT Foo AS fld1 FROM tblFoo'
At this point, it's just a string. But we can execute the contents of the string, as a dynamic SQL statement, using EXECUTE (or EXEC for short).
The old-school sp_executesql procedure is an alternative to EXEC, another way to execute dymamic SQL, which also allows you to pass parameters, rather than specifying all values as literals in the text of the statement.
FOLLOWUP
EBarr points out (correctly and importantly) that this approach is susceptible to SQL Injection.
Consider what would happen if #KeyValue contained the string:
'1 AS foo; DROP TABLE students; -- '
The string we would produce as a SQL statement would be:
'INSERT INTO #tmpTbl1 SELECT 1 AS foo; DROP TABLE students; -- AS fld1 ...'
When we EXECUTE that string as a SQL statement:
INSERT INTO #tmpTbl1 SELECT 1 AS foo;
DROP TABLE students;
-- AS fld1 FROM tbl1 AS foo; DROP ...
And it's not just a DROP TABLE that could be injected. Any SQL could be injected, and it might be much more subtle and even more nefarious. (The first attacks can be attempts to retreive information about tables and columns, followed by attempts to retrieve data (email addresses, account numbers, etc.)
One way to address this vulnerability is to validate the contents of #KeyValue, say it should contain only alphabetic and numeric characters (e.g. check for any characters not in those ranges using LIKE '%[^A-Za-z0-9]%'. If an illegal character is found, then reject the value, and exit without executing any SQL.
You could make use of Prepared Stements like this.
set #query = concat( "select name from " );
set #query = concat( "table_name"," [where condition] " );
prepare stmt from #like_q;
execute stmt;

T-SQL Foreach Loop

Scenario
I have a stored procedure written in T-Sql using SQL Server 2005.
"SEL_ValuesByAssetName"
It accepts a unique string "AssetName".
It returns a table of values.
Question
Instead of calling the stored procedure multiple times and having to make a database call everytime I do this, I want to create another stored procedure that accepts a list of all the "AssetNames", and calls the stored procedure "SEL_ValueByAssetName" for each assetname in the list, and then returns the ENTIRE TABLE OF VALUES.
Pseudo Code
foreach(value in #AllAssetsList)
{
#AssetName = value
SEL_ValueByAssetName(#AssetName)
UPDATE #TempTable
}
How would I go about doing this?
It will look quite crippled with using Stored Procedures. But can you use Table-Valued Functions instead?
In case of Table-Valued functions it would look something like:
SELECT al.Value AS AssetName, av.* FROM #AllAssetsList AS al
CROSS APPLY SEL_ValuesByAssetName(al.Value) AS av
Sample implementation:
First of all, we need to create a Table-Valued Parameter type:
CREATE TYPE [dbo].[tvpStringTable] AS TABLE(Value varchar(max) NOT NULL)
Then, we need a function to get a value of a specific asset:
CREATE FUNCTION [dbo].[tvfGetAssetValue]
(
#assetName varchar(max)
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT 0 AS AssetValue
UNION
SELECT 5 AS AssetValue
UNION
SELECT 7 AS AssetValue
)
Next, a function to return a list AssetName, AssetValue for assets list:
CREATE FUNCTION [dbo].[tvfGetAllAssets]
(
#assetsList tvpStringTable READONLY
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT al.Value AS AssetName, av.AssetValue FROM #assetsList al
CROSS APPLY tvfGetAssetValue(al.Value) AS av
)
Finally, we can test it:
DECLARE #names tvpStringTable
INSERT INTO #names VALUES ('name1'), ('name2'), ('name3')
SELECT * FROM [Test].[dbo].[tvfGetAllAssets] (#names)
In MSSQL 2000 I would make #allAssetsList a Varchar comma separated values list. (and keep in mind that maximum length is 8000)
I would create a temporary table in the memory, parse this string and insert into that table, then do a simple query with the condition where assetName in (select assetName from #tempTable)
I wrote about MSSQL 2000 because I am not sure whether MSSQL 2005 has some new data type like an array that can be passed as a literal to the SP.