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.
Related
I have a PL/SQL procedure that consumes two input parameters of type VARCHAR2 (amount is nullable, exact_amount is just a true/false flag but Oracle does not support boolean types):
amount in VARCHAR2,
exact_amount in VARCHAR2,
In the procedure, I build a select using execute immediate statement:
execute immediate 'select ... from ... where ...
and (nvl(:amount, ''null'') = ''null'' or (:exact_amount = ''1'' and TO_CHAR(TOTAL_VALUE) = :amount) or (:exact_amount = ''0'' and (TO_CHAR(TOTAL_VALUE) = :amount OR TO_CHAR(TOTAL_VALUE) LIKE '''||amount||'.%'')))'
bulk collect into ids
using ... amount, exact_amount, amount, exact_amount, amount;
But the problem is, somehow the condition always evaluates to false and the whole select does not return any results. I have tried many combinations of binding varchar params but none of them work properly so my question is what is the correct syntax?
When constructing string for execute immediate there is usualy a problem with single quotes. Try to get your sql command using something like this:
SET SERVEROUTPUT ON
Declare
cmd VarChar2(1000);
sq VarChar2(1) := ''''; -- single quote character
Begin
cmd := 'select col_1, col_2, col_3
from a_table
where numeric_column = ' || :numeric_bind_var || ' and char_column = ' || sq || :char_bind_vara || sq;
DBMS_OUTPUT.PUT_LINE(cmd);
End;
... if your :numeric_bind_var is 100 and your :char_bind_var is 'ABC' then the sql command will be:
select col_1, col_2, col_3
from a_table
where numeric_column = 100 and char_column = 'ABC'
I use sq (single_quote) variable declared and initialized with '''' (four single quotes) which is represented as a single quote in construction of a sql command.
When you get a working sql command contained in the cmd variable then you can do
-- ... ... ...
execute immediate cmd
-- ... ... ...
Puting two single quotes like ''null'' or ''1'' will end up throwing an error. Regards...
I would like to read user query from variable as (:user_query:) and execute it - in Oracle it will be like:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := 'SELECT 100 FROM dual';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( ' || user_query || ') AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry;
END;
I need to insert result of user_query to table 'a'.
I don't care if it will fail or sth, but is this safe? :) Is there any option if string user_query with SQL will drop my database or do sth else?
Or sb can construct a query that will drop my database?
If we fix the (many) syntax errors in your PL/SQL block we come to:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := '1024';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( :user_query ) AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry USING user_query;
END;
/
It will work and there is not a SQL injection vulnerability as it uses a bind variable :user_query to input the value.
However, that does not mean that it is particularly good as you:
Do not need to use dynamic SQL;
Do not need to select from the DUAL table; and
Do not need to explicitly CAST the input to a NUMBER as, if the column you are inserting into is a NUMBER then, there will be an implicit cast.
So the above code can be simplified to:
DECLARE
user_query VARCHAR2(20) := '1024';
BEGIN
INSERT INTO a (v) VALUES ( user_query );
END;
/
There is still no SQL injection vulnerability and the query is much simpler.
db<>fiddle here
Update
in Oracle it will be like:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := 'SELECT 100 FROM dual';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( ' || user_query || ') AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry;
END;
That has huge SQL injection vulnerabilities.
If user_query is, instead, set to:
SELECT CASE
WHEN EXISTS(
SELECT 1
FROM users
WHERE username = 'Admin'
AND password_hash = STANDARD_HASH( 'my$ecretPassw0rd', 'SHA256' )
)
THEN 100
ELSE 0
END
FROM DUAL
If you get the value 100 then you know that:
There is a table called users;
It has columns username and password_hash;
There is an Admin user; and
You've verified their password.
Please don't use dynamic SQL and string concatenation if you do not need to.
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.
I'm trying to write a code which update my table base on table2.
Table1 contain column like: COLUMN1,COLUMN2,COLUMN3...
Table2 contain column 2 column:
-First column contain name of the column from the table1 which should be update
-Second contain VALUE which should be set
So Table2 output:
columnname,value
----------------
COLUMN1 , 'sometext'
COLUMN2 , 'somethingelse'
set serveroutput on;
declare cursor doupdate
is
select columnname,value from TABLE2;
nazwa TABLE2.columnname%type;
wartosc TABLE2.value%type;
begin
open doupdate;
loop
fetch doupdate into nazwa,wartosc;
exit when doupdate%notfound;
update table1 set nazwa=wartosc;
end loop;
end;
While trying to run that code I got an error message which says:
PL/SQL: ORA-00904: "NAZWA": niepoprawny identyfikator
ORA-06550: linia 12, kolumna 1:
PL/SQL: SQL Statement ignored
What I'm doing wrong? Same types of columns - Varchar2(200 bytes)
Edit. there is a problem only with NAME... Anyone know the solution?...
EDIT2. I dit it and it works fine. I used dynamically SQL so it looks like: execute immediate 'update acc SET '||nazwa||'='||wartosc; . Could anyone explain why that?:)
This is dynamic sql, you have to execute it with Execute immediate command:
declare
cursor doupdate
is
select columnname,value from TABLE2;
nazwa TABLE2.columnname%type;
wartosc TABLE2.value%type;
dyn_sql varchar2(500);
begin
open doupdate;
loop
fetch doupdate into nazwa,wartosc;
exit when doupdate%notfound;
dyn_sql := 'update table1 set ' || nazwa || '=' ||wartosc;
execute immediate dyn_sql
end loop;
end;
Edit
A for loop and using clause will make things much simpler.
declare
dyn_sql varchar2(500);
begin
for i in (select columnname,value from TABLE2) loop
dyn_sql := 'update table1 set ' || i.columnname || ' = :a';
execute immediate dyn_sql using i.value
end loop;
end;
bind variable (execute immediate/using) will solve your problem of smth, smth2 and 'smth, smth2'
Do you have a column named 'nazwa' in table1?
If you don't have, that will be the problem.
If you have, I guess that confuses the Oracle server, because it can't decide whether you think about that column or the variable named nazwa in your code. In that case you should choose another name for your variable.
Edit: maybe you're missing WHERE clause from you UPDATE.
create or replace procedure PROC_MYDATA (inputStr IN VARCHAR2,
p_RecordSet IN OUT SYS_REFCURSOR) is
begin
OPEN p_RecordSet FOR
(select * from myTable where name in (inputStr));
end PROC_MYDATA;
In the PLSQL Test window, I am trying to set,
inputStr = 'A','B'
and I am getting this error:
ORA-01722: invalid number
I also tried to put escape character for single quote.
inputStr = '''A''','''B'''
Same error.
Can someone please help me understand what am I doing wrong?
I'm afraid it doesn't work this way:
SELECT * from myTable where name in (inputStr);
You can use dynamic SQL, as in #Bob Jarvis' answer, or you can do the following:
SELECT * FROM myTable WHERE REGEXP_LIKE(name, '^(' || REPLACE(inputStr, ',', '|') || ')$');
The difficulty with the latter is that, in Oracle, a regular expression can be at most 512 bytes long. So your inputStr would be limited to 508 bytes (since we're adding four bytes for the anchors and the grouping).
To use a list of comma-separated values you'll need to build and execute the statement dynamically:
create or replace procedure PROC_MYDATA (inputStr IN VARCHAR2,
p_RecordSet IN OUT SYS_REFCURSOR)
is
strSql VARCHAR2(32767);
begin
strSql := 'select * from myTable where name in (' || inputStr || ')';
OPEN p_RecordSet FOR strSql;
end PROC_MYDATA;
You should use this with a string which contains the single-quote characters in it to delimit each string; thus, use
DECLARE
inputStr VARCHAR2(100);
csrCursor SYS_REFCURSOR;
BEGIN
inputStr = '''A'', ''B''';
PROC_MYDATA(inputStr, csrCursor);
-- ...code to use csrCursor;
CLOSE csrCursor;
END;
Share and enjoy.
This is faster
SELECT * from myTable where name in (select regexp_substr(inputStr,'[^,]+', 1, level) from dual
connect by regexp_substr(inputStr, '[^,]+', 1, level) is not null);