How do I pass multiple entries through an input parameter mapped from a Table Function in SAP HANA - 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;

Related

Table Variable in SQL Server Function from Input Columns

I would like to create a function that returns a column based on input from three other columns. As temporary tables are not allowed within functions, is it possible to create a table variable from three input columns?
CREATE FUNCTION dbo.convert_value(
#CustomerID VARCHAR(MAX),
#CustomerValue VARCHAR(MAX),
#CustomerDescription VARCHAR(MAX)
)
RETURNS FLOAT
AS BEGIN
DECLARE #CustomerTable TABLE (
UniquePatientUID VARCHAR(MAX),
ResultValue VARCHAR(MAX),
PracticeDescription VARCHAR(MAX)
);
-- How can I insert #UniquePatientUID, #ResultValue and #PracticeDescription into #CustomerTable
END
The context of this question is that I have a SQL script that uses temporary tables and many UPDATE and ALTER TABLE statements, that I need to convert into a function. That script begins with the three columns mentioned, and adds a fourth column, Converted_Value, which is calculated with several hundred lines of code and manipulating temporary tables. Is there any hope here?
A table variable insert is really not different than a regular insert. Don't use temp tables. You can alter the table as well, or just declare it initially with that fourth column and allow it to be NULL.
INSERT INTO #CustomerTable (UniquePatientUID, ResultValue, PracticeDescription)
VALUES(#CustomerID, #CustomerValue, #CustomerDescription);
Don't forget to return the FLOAT.
Table Variable is a table so, you can just use INSERT INTO ... VALUES....
INSERT INTO #CustomerTable (UniquePatientUID,ResultValue,PracticeDescription )
VALUES
(#UniquePatientUID, #ResultValue , #PracticeDescription)
Unless you need a table variable for some specific reason, why not just work with the variables as a derived table expression? i.e.
;with inputs (UniquePatientUID, ResultValue, PracticeDescription) as
(
select #UniquePatientUID, #ResultValue, #PracticeDescription
)
select *
from inputs
Table variables fall out of scope after the function call, and you can't pass table types in or out of functions either. So really all a table variable does here is serve as a means of place keeping that's more familiar to SQL developers. But they're not free, which is the only reason I'm curious what your use case is.
If you don't need to return them as a set or something similar, you can just interact with the variables directly too.

FnSplit not working for SQL stored procedure to take multiple parameters

I have a stored procedure that currently takes in one value(ChainId) for a parameter. I am trying to allow the user to select multiple values of(ChainId). My where statement is below. Could someone help point me in a better direction than I am going now. Currently the query will run and return no data if I select multiple values for the parameter.
WHERE EndAuth is null AND CL.CHIND in(
SELECT [Value] FROM dbo.FnSplit(#ChainId, ','))
ORDER BY CL.CHIND
This is a popular function in SQL Server, so I'll assume you're working with that. Make sure your parameter is of type Varchar(MAX). #ChainId is passed as your string (ideally for SSRS) and ',' is passed as your delimiter. In SSRS, if you have a text box for your users to manually enter multiple values, they will enter something like 'value1, value2, value3'.
Test this out:
Declare #Yes_No Varchar(Max)
Set #Yes_No = 'y,n'
Select #yes_no
Select * from SplitString('y,n',',')
Select * from SplitString(#Yes_No,',')
Your results will be
y,n
----
y
n
----
y
n
Why I say to use Varchar(Max) and not int, or Varchar(10) for example, is because that would stop the function from reading all the values prematurely.
Try this:
Declare #Yes_No Varchar(1)
Set #Yes_No = 'y,n'
Select * from SplitString(#Yes_No,',')
The result will be:
y
The reason is because the function only accepts a value of 1 character in length, and splits that. As you can see, there isn't much to split.
This is just the way SSRS accepts parameters. FN_Split isn't necessarily a built-in function, but a widely popular one designed to allow you to pass multiple values to a string, with a pre-specified delimiter. So make sure you also go to your parameter in the report and specify that it will allow for multiple values. You will also want to supply a list of potential values for your users to select from. You'll either do this by manually populating a small list or providing another data source in the form of a stored procedure or table.

INPUT a List to stored procedure

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

Table Variables in Azure Data Warehouse

In a SQL Server database, one can use table variables like this:
declare #table as table (a int)
In an Azure Data Warehouse, that throws an error.
Parse error at line: 1, column: 19: Incorrect syntax near 'table'
In an Azure Data Warehouse, you can use temporary tables:
create table #table (a int)
but not inside functions.
Msg 2772, Level 16, State 1, Line 6 Cannot access temporary tables
from within a function.
This document from Microsoft says,
◦Must be declared in two steps (rather than inline): ◾CREATE TYPE
my_type AS TABLE ...; , then ◾DECLARE #mytablevariable my_type;.
But when I try this:
create type t as table (a int);
drop type t;
I get this :
Msg 103010, Level 16, State 1, Line 1 Parse error at line: 1, column:
8: Incorrect syntax near 'type'.
My objective is to have a function in an Azure Data Warehouse which uses a temporary table. Is it achievable?
Edit Start Here
Note that I am not looking for other ways to create one specific function. I have actually done that and moved on. I'm a veteran programmer but an Azure Data Warehouse rookie. I want to know if it's possible to incorporate some concept of temporary tables in an Azure Data Warehouse function.
Ok, I believe this is what you are after.
Firstly, this uses a Table Value Function, which are significantly faster than Scalar or Multi-statement Table value Functions.
Secondly, there was no use for a Table Variable, or Temporary Table, just some good odd string manipulation, a bit of maths, and a CTE. Definitely no expensive WHILE loop.
I've tested this against the examples in the link, and they all return the expected values.
USE Sandbox;
GO
CREATE FUNCTION ValidateHealthNumber (#HealthNumber varchar(10))
RETURNS TABLE
AS
RETURN
WITH Doubles AS(
SELECT CONVERT(tinyint,SUBSTRING(V.HN,O.P,1)) AS HNDigit,
CONVERT(tinyint,SUBSTRING(V.HN,O.P,1)) * CASE WHEN O.P % 2 = 0 THEN 1 ELSE 2 END ToAdd
FROM (VALUES(#HealthNumber)) V(HN)
CROSS APPLY (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)) O(P)),
Parts AS (
SELECT CONVERT(tinyint,SUBSTRING(CONVERT(varchar(2),ToAdd),1,1)) AS FirstDigit, --We know that the highest value can be 18 (2*9)
CONVERT(tinyint,SUBSTRING(CONVERT(varchar(2),ToAdd),2,1)) AS SecondDigit --so no need for more than 2 digits.
FROM Doubles)
SELECT CASE RIGHT(#HealthNumber, 1) WHEN 10 - RIGHT(SUM(FirstDigit + SecondDigit),1) THEN 1 ELSE 0 END AS IsValid
FROM Parts;
GO
CREATE TABLE #Sample(HealthNumber varchar(10));
INSERT INTO #Sample
VALUES ('9876543217'), --Sample
('5322369835'), --Valid
('7089771195'), --Valid
('8108876957'), --Valid
('4395667779'), --Valid
('6983806917'), --Valid
('2790412845'), --not Valid
('5762696912'); --not Valid
SELECT *
FROM #Sample S
CROSS APPLY ValidateHealthNumber(HealthNumber) VHN;
GO
DROP TABLE #Sample
DROP FUNCTION ValidateHealthNumber;
If you don't understand any of this, please do ask.
No you can't. Object can't be created inside User Defined Functions (UDF). Use table variables instead.
If you want yo use user defined type, first create it outside the UDF and use it as a variable type within the UDF.
-- Create the data type
CREATE TYPE TestType AS TABLE
(
Id INT NOT NULL,
Col1 VARCHAR(20) NOT NULL)
GO
-- Create the tabled valued function
CREATE FUNCTION TestFunction
()
RETURNS
#Results TABLE
(Result1 INT, Result2 INT)
AS
BEGIN
-- Fill the table variable with the rows for your result set
DECLARE #Var1 TestType;
RETURN
END
GO

Discover DB2 procedure default parameters using SYSCAT tables

Like Oracle, DB2 supports parameter defaults in stored procedures. Oracle syntax:
CREATE OR REPLACE PROCEDURE p_default (
p_in_number IN number := 0,
p_out_number OUT number,
p_in_varchar IN varchar2 := '0',
p_out_varchar OUT varchar2,
p_in_date IN date := date '1981-07-10',
p_out_date OUT date
)
DB2 syntax:
CREATE PROCEDURE p_default (
IN p_in_number INTEGER DEFAULT(0),
OUT p_out_number INTEGER,
IN p_in_varchar VARCHAR(10) DEFAULT('0'),
OUT p_out_varchar VARCHAR(10),
IN p_in_date DATE DEFAULT('1981-07-10'),
OUT p_out_date DATE
)
With Oracle, I can discover defaults using this query:
SELECT argument_name, defaulted FROM all_arguments WHERE object_id = :proc_id
How can I discover this in DB2 selecting from SYSCAT tables? I don't see any useful column in SYSCAT.PROCPARMS or SYSCAT.FUNCPARMS. Note, I don't mind calling any stored procedure from SYSPROC if such a procedure exists...
Note, I have asked as similar question about SQL Server:
Discover SQL Server procedure default parameters using SYS or INFORMATION_SCHEMA tables
(This assumes you're looking for the information on DB2 Linux/Unix/Windows, it may vary for other platforms)
You can use the SYSCAT.ROUTINEPARMS catalog view to find this information. It lists all the parameter types that the function can accept (there can be multiple rows if the procedure has multiple signatures), and if applicable, their default (in the aptly-named DEFAULT column). If a default is not supplied, that column will be NULL.
For example, if you wanted to see the input parameters for SYSIBMADM.SUBMIT (which has optional parameters), you could use this query:
SELECT *
FROM SYSCAT.ROUTINEPARMS
WHERE ROUTINESCHEMA='SYSIBMADM'
AND ROUTINENAME ='SUBMIT'
AND ROWTYPE IN ('B', 'P')
ROWTYPE of B allows for both input and output variables, and P is for input-only. The other types are covered in the Info Center doc I linked above.