Dynamic update query with parameters in SAP HANA procedures - sql

I have a SAP HANA procedures to update a table. I'm trying to create a dynamic sql query, this is my example:
declare _field varchar(100) := 'NAME';
declare _name varchar(100) := 'david';
declare _id integer := 1;
DECLARE SQL_STR VARCHAR(3000);
SQL_STR := 'UPDATE "_SYS_BIC"."TEST_TABLE" SET "'||_field||'" = '||_name||' WHERE "ID" = '||:_id;
EXECUTE IMMEDIATE (:SQL_STR);
But in the console I have this error:
Service exception: [260] invalid column name
How can I fix it?

I found the correct syntax to fix it
declare _id integer := 1;
declare _name varchar(100) := 'david';
declare _field varchar(100) := 'NAME';
DECLARE SQL_STR VARCHAR(3000);
SQL_STR := 'UPDATE "_SYS_BIC"."TEST_TABLE" SET "'||_field||'" = '''||_name||''' WHERE "ID" = '||_id;
EXECUTE IMMEDIATE (:SQL_STR);
If I have to set a column or table name the right syntax is
"'||_field||'"
To set a string value the syntax is '''||_name||''' the first two apostrophes are used for the escape so we can say to the query that the value is a string, the last one apostrophe is used to concatenate the value to the rest of the query string. The three apostrophes must be written without spaces among them.

Related

SQL0103 The numeric literal "00XAB" is not valid SQLSTATE = 42604

I want to insert string for instance '7000,21,00XYZ, ABC'
when I use this as an insert into a table, I get the error as mentioned, please help me with this for DB2.
I am able to insert this when I do
INSERT INTO TABLE_NAME1(COLUMN1)
(SELECT statement)
But this is not working for Dynamic Sql.
Wrong dynamic sql use probably.
Consider the following example.
--#SET TERMINATOR #
SET SERVEROUTPUT ON#
BEGIN
DECLARE V_STR VARCHAR (20) DEFAULT '7000,21,00XYZ, ABC';
DECLARE V_STMT VARCHAR (100);
DECLARE V_CNT INT;
-- Incorrect use of a string constant:
-- SET V_STMT = 'SET ? = (SELECT COUNT (1) FROM (VALUES ' || V_STR || '))';
-- Correct use of a string constant:
SET V_STMT = 'SET ? = (SELECT COUNT (1) FROM (VALUES ''' || V_STR || '''))';
CALL DBMS_OUTPUT.PUT_LINE (V_STMT);
PREPARE S1 FROM V_STMT;
EXECUTE S1 INTO V_CNT;
CALL DBMS_OUTPUT.PUT_LINE ('CNT: ' || V_CNT);
END#
SET SERVEROUTPUT OFF#
If you forgot to wrap your string constant into single quotes and constructed an incorrect statement:
SET ? = (SELECT COUNT (1) FROM (VALUES 7000,21,00XYZ, ABC))
instead of the correct one:
SET ? = (SELECT COUNT (1) FROM (VALUES '7000,21,00XYZ, ABC'))
then you get the SQL0103N error as you showed: Db2 assumes that VALUES 7000,21,00XYZ, ABC is a table of a single column and 4 values, and since the literal 00XYZ starts from a number, then it's an attempt to provide a numeric constant which is not valid, of course.

db2 dynamic cursor declaration

I am trying to create a stored procedure, but I get this error
Expected tokens may include: "". LINE NUMBER=17. SQLSTATE=42601
My code:
CREATE OR REPLACE PROCEDURE FETCH_EMP_SP(IN V_EMP_NAME VARCHAR(100),IN V_EMP_DEPT VARCHAR(100))
DYNAMIC RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE p_query_string VARCHAR(100);
IF ((V_EMP_NAME IS NOT NULL) AND (V_EMP_DEPT IS NOT NULL)) THEN
SET p_query_string = 'emp_name ='||V_EMP_NAME||' AND emp_dept='||V_EMP_DEPT||' WITH UR';
ELSEIF(V_EMP_DEPT IS NOT NULL) THEN
SET p_query_string = ' AND emp_dept='||V_EMP_DEPT||' WITH UR';
ELSE
SET p_query_string = ' WITH UR';
END IF;
DECLARE C1 CURSOR WITH RETURN TO CLIENT FOR SELECT emp_name,emp_no,emp_dept,emp_location from employee where status=1 p_query_string;
OPEN C1;
END#
should be executed successfully
Declarations and statements can’t follow in arbitrary order in a Compound SQL (compiled) statement
Cursor declarations must follow the variables declarations and must be followed by the SQL procedure statements.
So, place the cursor declaration after the variable declaration.
Moreover, there is a number of other errors in your code. Should be something like this:
CREATE OR REPLACE PROCEDURE FETCH_EMP_SP(IN V_EMP_NAME VARCHAR(100),IN V_EMP_DEPT VARCHAR(100))
DYNAMIC RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE p_query_string VARCHAR(256);
DECLARE C1 CURSOR WITH RETURN TO CLIENT FOR S1;
IF ((V_EMP_NAME IS NOT NULL) AND (V_EMP_DEPT IS NOT NULL)) THEN
SET p_query_string = ' AND emp_name ='''||V_EMP_NAME||''' AND emp_dept='''||V_EMP_DEPT||''' WITH UR';
ELSEIF(V_EMP_DEPT IS NOT NULL) THEN
SET p_query_string = ' AND emp_dept='''||V_EMP_DEPT||''' WITH UR';
ELSE
SET p_query_string = ' WITH UR';
END IF;
SET p_query_string='SELECT emp_name,emp_no,emp_dept,emp_location from employee where status=1 '||p_query_string;
PREPARE S1 FROM p_query_string;
OPEN C1;
END#
String constants in the query text must be wrapped in single quotes. Don't do this if emp_dept is a numeric column.

HANA SQL function to split a comma delimited string into substrings

I have the following HANA Procedure:
CREATE PROCEDURE SP_LIT()
AS
BEGIN
DECLARE count INT;
DECLARE pos INT;
DECLARE value NVARCHAR(100);
value := 'R,A';
IF LENGTH(:value) > 0 THEN
value := :value + ',';
pos := LOCATE(:value,',',1);
END IF;
WHILE :pos > 0 DO
BEGIN
INSERT INTO [O/P table] VALUES (LEFT(:value,:pos-1));
value := RIGHT(:value, LENGTH (:value)-:pos);
pos := LOCATE(:value,',',1);
END;
END WHILE;
END;
Everything seems fine but on execution the following error is thrown:
Error: invalid number exception: invalid number: not a valid number string 'R,A'
Any idea where am I going wrong?
I found the solution.
The '+' sign in concatenation should be replaced by '||' pipe operator.

HANA - Passing string variable into WHERE IN() clause in SQL script

Lets suppose I have some SQL script in a scripted calculation view that takes a single value input parameter and generates a string of multiple inputs for an input parameter in another calculation view.
BEGIN
declare paramStr clob;
params = select foo
from bar
where bar.id = :IP_ID;
select '''' || string_agg(foo, ''', ''') || ''''
into paramStr
from :params;
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"(PLACEHOLDER."$$IP_IDS$$" => :paramStr);
END
This works as expected. However, if I change the var_out query and try to use the variable in a where clause
BEGIN
...
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
where "IP_IDS" in(:paramStr);
END
the view will activate, but I get no results from the query. No runtime errors, just an empty result set. When I manually pass in the values to the WHERE IN() clause, everything works fine. It seems like an elementary problem to have, but I can't seem to get it to work. I have even tried using char(39) rather than '''' in my concatenation expression, but no banana :(
Ok, so what you are doing here is trying to make the statement dynamic.
For the IN condition, you seem to hope that once you have filled paramStr it would be handled as a set of parameters.
That's not the case at all.
Let's go with your example from the comment: paramStr = ' 'ip1','ip2' '
What happens, when the paramStr gets filled into your code is this:
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
where "IP_IDS" in(' ''ip1'',''ip2'' ');
So, instead of looking for records that match IP_DS = 'ip1' or IP_DS = 'ip2' you are literally looking for records that match IP_DS = ' 'ip1','ip2' '.
One way to work around this is to use the APPLY_FILTER() function.
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW";
filterStr = ' "IP_IDS" in (''ip1'',''ip2'') ';
var_out_filt = APPLY_FILTER(:var_out, :filterStr) ;
I've written about that some time ago: "On multiple mistakes with IN conditions".
Also, have a look at the documentation for APPLY_FILTER.
The SAP note “2315085 – Query with Multi-Value Parameter on Scripted Calculation View Fails with Incorrect Syntax Error“ actually showcases the APPLY_FILTER() approach that failed when I first tried it.
It also presents an UDF_IN_LIST function to convert a long varchar string with array of items from input parameter to a usable in-list predicate.
Unfortunately, couldn't make it work in sps11 rev 111.03 despite some parameter available (SAP Note 2457876 :to convert error into warning for string length overflow) - (range 3) string is too long exception
then ALTER SYSTEM ALTER CONFIGURATION ('indexserver.ini', 'System') set ('sqlscript', 'typecheck_procedure_input_param') = 'false' WITH RECONFIGURE;
-recompile the Calc View
then
select * from :var_tempout where OBJECT_ID in (select I_LIST from "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(:in_objectids,','));
invalid number exception - invalid number
But taking the scripting out of the function makes it work...
For this SPS 11 level APPLY_FILTER seems to be the only workaround. And it is really hard to say what would be the rev on SPS 12 to go in order to have it.
FUNCTION "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(str_input nvarchar(5000),
delimiter nvarchar(10))
RETURNS table ( I_LIST INTEGER ) LANGUAGE SQLSCRIPT SQL SECURITY INVOKER AS
/********* Begin Function Script ************/
BEGIN
DECLARE cnt int;
DECLARE temp_input nvarchar(128);
DECLARE slice NVARCHAR(10) ARRAY;
temp_input := :str_input;
cnt := 1;
WHILE length(temp_input) > 0 DO
if instr(temp_input, delimiter) > 0 then
slice[:cnt] := substr_before(temp_input,delimiter);
temp_input := substr_after(temp_input,delimiter);
cnt := :cnt + 1;
else
slice[:cnt] := temp_input;
break;
end if;
END WHILE;
tab2 = UNNEST(:slice) AS (I_LIST);
return select I_LIST from :tab2;
END;
CREATE PROCEDURE "MY_SCRIPTED_CV/proc"( IN numbers NVARCHAR(5000), OUT
var_out
MY_TABLE_TYPE ) language sqlscript sql security definer reads sql data with
result view
"MY_SCRIPTED_CV" as
/********* Begin Procedure Script ************/
BEGIN
-- not working
--var_out = select * from MY_TABLE where NUMBER in (select I_LIST from
--UDF_INLIST_P(:numbers,','));
-- working
DECLARE cnt int;
DECLARE temp_input nvarchar(128);
DECLARE slice NVARCHAR(13) ARRAY;
DECLARE delimiter VARCHAR := ',';
temp_input := replace(:numbers, char(39), '');
cnt := 1;
WHILE length(temp_input) > 0 DO
if instr(temp_input, delimiter) > 0 then
slice[:cnt] := substr_before(temp_input,delimiter);
temp_input := substr_after(temp_input,delimiter);
cnt := :cnt + 1;
else
slice[:cnt] := temp_input;
break;
end if;
END WHILE;
l_numbers = UNNEST(:slice) AS (NUMBER);
var_out=
SELECT *
FROM MAIN AS MA
INNER JOIN l_numbers as LN
ON MAIN.NUMBER = LN.NUMBER
END;
I know, this is a quite old thread but nevertheless my findings based what I read in here from Jenova might be interesting for others. Thatswhy I am writing them down.
I was faced with the same problem. Users can sent multiple entrie in an input parameter in a calc view I have to optimize. Unluckily I run into the same problem like Jenova and others before. And, no, imho this is not about dynamic view execution or dynamic sql, but about processing in lists in a clean way in a scripted calc view or table function if they are send in an input parameter.
Lars' proposal to use APPLY_FILTER is not applicable in my case, since I cannot use a complex statement in the APPLY_FILTER function itself. I have to materialize the whole amount of data in the select, which result I can assign to APPLY_FILTER and then execute the filtering. I want to see the filtering applied in the first step not after materializing data which is not appearing in the result of the filter application.
So I used a hdbtablefunction to create a function performing the parameter string cleanup and the transformation into an array and afterwards it is returning the result of the UNNEST function.
Since the result of the function is a table it can be used as a table inside the scripted view or tablefunction - in joins, subselects and so on.
This way I am able to process user input lists as expected, fast and with much less resource consumption.
FUNCTION "_SYS_BIC"."package1::transparam" (ip_string NVARCHAR(500) )
RETURNS table ( "PARAMETER" nvarchar(100))
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER AS
v_test varchar(1000);
IP_DELIMITER VARCHAR(1) := ',';
v_out VARCHAR(100):='';
v_count INTEGER:=1;
v_substr VARCHAR(1000):='';
v_substr2 VARCHAR(1000):='';
id INTEGER array;
val VARCHAR(100) array;
BEGIN
--
v_substr:=:ip_string;
v_substr := REPLACE(:v_substr, '''', '');
v_substr := REPLACE(:v_substr, ' ', '');
while(LOCATE (:v_substr, :ip_delimiter) > 0 ) do
-- find value
v_out := SUBSTR(v_substr, 0, LOCATE (:v_substr, :ip_delimiter) - 1 );
-- out to output
val[v_count]:=v_out;
-- increment counter
v_count:=:v_count+1;
-- new substring for search
v_substr2 := SUBSTR(:v_substr, LOCATE (:v_substr, :ip_delimiter) + 1, LENGTH(:v_substr));
v_substr := v_substr2;
END while;
IF(LOCATE (:v_substr, :ip_delimiter) = 0 AND LENGTH(:v_substr) > 0) THEN
-- no delimiter in string
val[v_count]:=v_substr;
END IF;
-- format output as tables
rst = unnest(:VAL) AS ("PARAMETER");
RETURN SELECT * FROM :rst;
END;
can be called like
select * from "package1.transparam"('''BLU'',''BLA''')
returning table with two lines
PARAMETER
---------
BLU
BLA
The most comprehensive explanation is here:
https://blogs.sap.com/2019/01/17/passing-multi-value-input-parameter-from-calculation-view-to-table-function-in-sap-hana-step-by-step-guide/
Creating custom function splitting string into multiple values
Then inner/left outer join can be used to filter.

Delphi - pass table valued parameter to SQL Server stored procedure

I need to pass the parameter as table value for a stored procedure in SQL Server. How to handle this in Delphi?
As far as I know there in no simple way to pass Table parameters, using the components shipped with Delphi.
A workaround would be using a temporary table which can be used to fill a typed table variable.
Assuming your definition would look like this:
CREATE TYPE MyTableType AS TABLE
( ID int
, Text varchar(100) )
GO
CREATE PROCEDURE P_Table
#Tab MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
Select * from #Tab -- dummy operation just return the dataset
END
GO
You could call the procedure like this:
var
i: Integer;
begin
// we create a temporary table since a table variable can obly be used for a single call
DummyDataset.Connection.Execute('Create Table #mytemp(ID int,Text varchar(100))');
DummyDataset.CommandText := 'Select * from #mytemp';
DummyDataset.Open;
for i := 0 to 10 do
begin
DummyDataset.Append;
DummyDataset.Fields[0].Value := i;
DummyDataset.Fields[1].Value := Format('A Text %d', [i]);
DummyDataset.Post;
end;
MyDataset.CommandText := 'Declare #mytemp as MyTableType '
+ 'Insert into #mytemp select * from #mytemp ' // copy data to typed table variable
+ 'EXEC P_Table #Tab = #mytemp';
MyDataset.Open;
DummyDataset.Connection.Execute('Drop Table #mytemp');
end
The sample downloadable from http://msftdpprodsamples.codeplex.com/wikipage?title=SS2008%21Readme_Table-Valued%20Parameters is written in C++ but could readily be translated to Delphi.
Once you have translated that code to Delphi, you can use something like the following to make the result set accessible via good ole ADO:
SourcesRecordset := CreateADOObject(CLASS_Recordset) as _Recordset;
RSCon := SourcesRecordset as ADORecordsetConstruction;
RSCon.Rowset := rowset;
LDataSet := TADODataSet.Create(nil);
try
// Only doing the first result set
LDataSet.Recordset := SourcesRecordset;
while not LDataSet.Eof do
begin
//... something
LDataSet.Next;
end;
finally
LDataSet.Free;
end;
Note that CreateADOObject is a private function in Data.Win.ADODB.pas but it's pretty trivial.