Error SQL0104 when creating a function in System i V7R1 - sql

I'm creating a SQL function on System i V7R1:
CREATE FUNCTION MYSCHEMA.GROUPDIBAS(v_code VARCHAR(50))
RETURNS VARCHAR(2048)
LANGUAGE SQL
BEGIN
DECLARE str VARCHAR(2048);
SET str = '';
FOR row AS (
SELECT
FIELD2
FROM MYSCHEMA.DIBAS
WHERE FIELD1 = v_code
)
DO
SET str = 'Bubi'; --I removed many statements to make clear the problem doesn't come from them
END FOR;
RETURN str;
END
;
I execute it with "Run SQL script" tool, which is part of the iSeries Navigator V7R1.
It works on another V7R1 server (using iSeries Navigator V5R4), but not in that one where I'm working now. It fails with this message:
SQL State: 42601
Vendor Code: -104
Message: [SQL0104] Token <END-OF-STATEMENT> was not valid. Valid tokens: ;.
Cause . . . . . : A syntax error was detected at token <END-OF-STATEMENT>.
Token <END-OF-STATEMENT> is not a valid token. A partial list of valid tokens is ;.
This list assumes that the statement is correct up to the token.
The error may be earlier in the statement, but the syntax of the statement appears to be valid up to this point.
Recovery . . . : Do one or more of the following and try the request again:
-- Verify the SQL statement in the area of the token <END-OF-STATEMENT>. Correct the statement.
The error could be a missing comma or quotation mark, it could be a misspelled word, or it could be related to the order of clauses.
-- If the error token is <END-OF-STATEMENT>, correct the SQL statement because it does not end with a valid clause.
If I remove the FOR block, it works.
Moreover if I execute the statement with 5250 Emulator, command STRSQL, it works. So it seems like a bug in "Run SQL script" client.
Any hint will be appreciated!

The issue is with the FOR statement. The query analyzer is inconsistent on when the cursor-name CURSOR FOR is optional and when it is required even though the documentation states if it is not specifified a unique cursor name is generated. SQL submitted via the IBM Access Navigator Run Scripts utility require it.
The parenthesis are also incorrect but sometimes they are accepted (STRSQL, Navigator Run SQL Scripts) and sometimes they aren't (DBVisualizer/JDBC).
TIL there must be a different query analyzer running depending on the source of the query.
CREATE FUNCTION MYSCHEMA.GROUPDIBAS(v_code VARCHAR(50))
RETURNS VARCHAR(2048)
LANGUAGE SQL
BEGIN
DECLARE str VARCHAR(2048);
SET str = '';
FOR row AS C1 CURSOR FOR
SELECT
FIELD2
FROM MYSCHEMA.DIBAS
WHERE FIELD1 = v_code
DO
SET str = 'Bubi'; --I removed many statements to make clear the problem doesn't come from them
END FOR;
RETURN str;
END

Given the tests made by #JamesA and me, I fear the problem can be in the Program Temporary Fix (PTF) that this server hasn't and the other ones have. Specifically, running WRKPTFGRP command, I can guess it probably misses this PTF group:
PTF group Level Text
SF99701 5 DB2 FOR IBM I
Unfortunately I can't try installing it now :(.

In the session properties of your IDE change the Statement Separator field value from ; to | then reconnect your session. then use | instead of ;. this way you can run your statement or procedure or function.
usage example,
CREATE FUNCTION MYSCHEMA.GROUPDIBAS(v_code VARCHAR(50))
RETURNS VARCHAR(2048)
LANGUAGE SQL
BEGIN
DECLARE str VARCHAR(2048);
SET str = '';
FOR row AS C1 CURSOR FOR
SELECT
FIELD2
FROM MYSCHEMA.DIBAS
WHERE FIELD1 = v_code
DO
SET str = 'Bubi'; --I removed many statements to make clear the problem doesn't come from them
END FOR;
RETURN str;
END |

Related

Trying to get HANA Create function to work in ODBC (warning in hdbsql becomes error in ODBC)

I have the following function that we have working in SQL Server, in PostGreSQL, and a version of this working in Oracle (not with a SELECT INTO but using a ref cursor).
Our function in HANA will compile with warnings on the hdbsql command line, but when executed within an ODBC call, it throws an exception. The database I'm using for testing is (unfortunately) a Version 1 db.
CREATE OR REPLACE FUNCTION GETWFFIELD
(
IN TABLENAME VARCHAR,
IN FIELDNAME VARCHAR,
IN LANGCODE VARCHAR,
IN WORKID DECIMAL,
IN SUBWORKID DECIMAL,
IN TASKID DECIMAL,
IN DEFVAL VARCHAR
) RETURNS SQLResults NVARCHAR(2000)
AS
BEGIN
IF TASKID IS NULL THEN
SELECT TOP 1 Value
INTO SQLResults DEFAULT DEFVAL
FROM WML
WHERE TableName=TABLENAME
AND ColName=FIELDNAME
AND Key1=WORKID
AND Key2=SUBWORKID
AND LangCode=LANGCODE;
ELSE
SELECT TOP 1 Value
INTO SQLResults DEFAULT DEFVAL
FROM WML
WHERE TableName=TABLENAME
AND ColName=FIELDNAME
AND Key1=WORKID
AND Key2=SUBWORKID
AND Key3=TASKID
AND LangCode=LANGCODE;
END IF;
END;
I would have used a cursor but unlike Oracle, I couldn't find an example in HANA as to how to call one SQL or another plus that seems to be Dynamic SQL which HANA forbids in functions (but the above is allowed).
My issue with the above is that when I execute it in our GUI, it executes in ODBC not in a SQL console, and the warning becomes an error message:
E675217410:ODBC reported error.:[SAP AG][LIBODBCHDB SO][HDBODBC] General error; > 1347 Not recommended feature: Using SELECT INTO in Scalar UDF
Really there are two elements to this that I need:
the ability to have a slightly different query in HANA when the value TASKID is null or not null (i.e. if it's null, it's not part of the WHERE clause), and
that the query return the passed in default if no results are found.
Is there a way to get this to run in an ODBC call?
Thanks in advance
Do not use dynamic SQL until there's no really dynamic identifiers or dynamic code.
You write this query one time, but then such dynamic SQL will waste resources with every execution, so it is worth to invest more development time to have a single or copied static SQL.
What about answers:
Your statement can be reduced to single statement with ... and ( (:taskid is not null and Key3=:taskid) or :taskid is null) and no dynamic SQL. If you really need to have a dynamic SQL to return something, then you are limited to only scalar output for HANA 2.0 and no parameter binding at all (neither in, nor out) for HANA 1.0 (do not know if this works fine in 2.0).
This can be achieved with aggregation function on your value since aggregation function always return a value (null if no rows were passed where clause), If you want to have a single value with TOP (maybe you need a first row in some order), then you can wrap it into a subquery and then do a dummy aggregation on one row.
SELECT coalesce(max(Value), 'Your default value')
INTO SQLResults
FROM WML
WHERE ...
Or you may use exception handlers, but they are too bulky and with some not visually obvious error codes.
do begin
declare lv_str varchar(100);
begin
DECLARE EXIT HANDLER FOR SQL_ERROR_CODE 1299 /*no data found*/
lv_str := 'Default value';
select 10
into lv_str
from dummy
where 1 = 0;
end;
select :lv_str as res from dummy;
end;
| | RES |
+---+---------------+
| 1 | Default value |
What about the code: it is better to use colon in front of the variable not to mix it with table columns: if you'll have a typo, your query will evaluate, for example, LangCode=LANGCODE as always true for non-nulls and make the debug process hard.

SQL syntax error in updating a database value

I'm update the money for one person only in a database. The money is saved as a currency and the email as a string. My SQL is throwing a syntax error
ADOQuery.sql.text:= ' UPDATE TblPlayerdetails SET Money = "' + Inttostr(NewAmount) + '" WHERE Email = "' + Playersemail + '"';
Newamount is an integer and email is a string.
I was hoping you would manage to work out what to do from the documentation I linked in comments, but on reflection I thought I had better provide a correct answer.
Set up the following code
procedure TForm1.Button1Click(Sender: TObject);
begin
AdoQuery2.SQL.Text := 'update moneytable set money = :money where id = :id';
AdoQuery2.Parameters.ParamByName('ID').Value := 1;
AdoQuery2.Parameters.ParamByName('Money').Value := 99;
AdoQuery2.ExecSQL;
end;
The line
AdoQuery2.SQL.Text := 'update moneytable set money = :money where id = :id';
sets up a parameterised UPDATE statement. The :id and :money are placeholders for parameter values which will be provided separately. The parameter names are ID and Money, though they could be given other names. Note that you could set up AdoQuery2's SQL.Text in the IDE at design time if you wanted to.
The next two lines
AdoQuery2.Parameters.ParamByName('ID').Value := 1;
AdoQuery2.Parameters.ParamByName('Money').Value := 99;
specify the values which the parameters are to be set to for when the UPDATE is actually executed. The ID value is the Row iD (aka primary key) of the row in the table which is to be updated. Before the UPDATE staement is actually executed, the AdoQuery parses the SQL and creates the parameters if they don't alteady exist (you can create them at design time in the IDE by editing the Parameters property of the AdoQuery.
Finally
AdoQuery2.ExecSQL;
is what actually executes the UPDATE statement. Note that you can repeat the steps of setting the parameter values and calling ExecSQL as many times as you want.
The main thing which was wrong with your UPDATE statement was that you were using double-quote (") marks, whereas when a SQL statement needs quote marks (and values of numeric columns do NOT) they should be single quotes('). A complication when constructing a SQL statement in Delphi code is that its syntax requires single quotes which are to be embedded in the SQL to be doubled up.
Note also that you should always used parameterised SQL for your SELECT, UPDATE, INSERT and DELETE statements as this helps protect your app against Sql injection. Making, say, an unparameterised statement accessible to the user can allow a malicious user to attempt to execute any SQL they wish.
In your question, you did not indicate what type of column is 'Money'. If it's varchar, char, then I understand why you might convert NewAmount to a string.
However, if the database expects numeric value (because the field is of type int, double, dec, or float), the syntax would be SET Money= '+ NewAmount +'.

SQLRPGLE syntax for Exec sql from a varying length variable?

On IBMi (database is DB2 for i) in SQLRPGLE I have a program that builds a large SQL statement into a variable that I would like to run.
When I try to run it as a variable I receive a token error
Some background
Here is an example that works because it does not use a variable
Exec SQL
Create table MyLib/MyFile as(select * from XXLIB/XXFILE)
DATA INITIALLY DEFERRED REFRESH DEFERRED
maintained by user;
When I save this in a variable like #SQLStm and then try to execute as SQL
Exec SQL
:#SQLStm;
I get the error
Token : was not valid. Valid tokens: .
Also I am open to different approaches
https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/cl/runsqlstm.htm
Like RUNSQLSTM SRCFILE(MYLIB/MYFILE) SRCMBR(MYMBR)
Maybe there is a way to take a variable and save it to a source member?
Then use RUNSQLSTM over the source member
Showing some code:
Definition for the variable
d #SQLStm s A Len(6144) Varying(4)
Even when trying a portion of the SQL statement as a variable
#SQLStm = select * from XXLIB/XXFILE;
and then try:
Exec SQL
Create table MyLib/MyFile as( :#SQLStm)
DATA INITIALLY DEFERRED REFRESH DEFERRED
maintained by user;
I get the error
Token : was not valid. Valid tokens: .
I expect the SQLRPLE to compile
Instead of SQL precompile failed.
MSG ID SEV RECORD TEXT
SQL0104 30 236 Position 31 Token : was not valid. Valid tokens:
.
Message Summary
Total Info Warning Error Severe Terminal
1 0 0 0 1 0
30 level severity errors found in source
This is static SQL
Exec SQL
Create table MyLib/MyFile as(select * from XXLIB/XXFILE)
DATA INITIALLY DEFERRED REFRESH DEFERRED
maintained by user;
What you want is dynamic SQL
wSqlStmt = 'Create table MyLib/MyFile as(select * from XXLIB/XXFILE)'
+ ' DATA INITIALLY DEFERRED REFRESH DEFERRED'
+ ' maintained by user';
exec SQL
execute immediate :wSqlStmt;
Note that some statements can't be execute immediate instead you have to prepare then execute them.
more information can be found in the Embedded SQL programming manual.

Two simple MySQL statements causing syntax error

I'm confounded. The following MySQL query:
SET #a := 0;
SELECT *
FROM users;
Gives the error:
Invalid query: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT * FROM users' at line 2`
When I switch the order of the statements, I get the same error, again on line 2 (even though I switched them)
However, either line by themselves runs fine. What could possibly cause this?
I bet you're trying to perform this query in the mysql_query() (or some similar function from any programming language), but it accepts only single query. So the solution is to split this queries into 2 calls.
you can do it in one query as follows:
The trick
select #a:=#a+1, u.*
from
users u
join (select #a:=0) a
or be adventerous and use a stored procedure so it's always a single call :P
Stored procedure
drop procedure if exists list_users;
delimiter #
create procedure list_users()
begin
set #a = 0;
select #a:=#a+1, u.* from users u;
end #
delimiter ;
call list_users();
PHP script
$conn = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);
$result = $conn->query("call list_users()");
while($row = $result->fetch_assoc()){
...
}
$result->close();
$conn->close();

Debug SQL in pgAdmin when SQL contains variables

In SQL Server I could copy sql code out of an application and paste it into SSMS, declare & assign vars that exist in the sql and run. yay great debugging scenario.
E.g. (please note I am rusty and syntax may be incorrect):
declare #x as varchar(10)
set #x = 'abc'
select * from sometable where somefield = #x
I want to do something similar with Postgres in pgAdmin (or another postgres tool, any recommendations?) where I can just drop my SQL (params & all) into something that will run against Postgres DB.
I realise you can create pgscript, but it doesn't appear to be very good, for example, if I do the equivalent of above, it doesn't put the single quotes around the value in #x, nor does it let me by doubling them up and you don't get a table out after - only text...
Currently I have a piece of SQL someone has written that has 3 unique variables in it which are used around 6 times each...
So the question is how do other people debug SQL efficiently, preferably in a similar fashion to my SQL Server days.
You can achieve this using the PREPARE, EXECUTE, DEALLOCATE commands for handling statements, which is really what we are talking about here.
For example:
PREPARE test AS SELECT * FROM users WHERE first_name = $1;
EXECUTE test ('paul');
DEALLOCATE test;
Perhaps not as graphical as some may like, but certainly workable.
I would give a shot at writing a SQL function that wraps your query. It can be something as simple as
CREATE OR REPLACE FUNCTION my_function(integer, integer)
RETURNS integer
AS
$$
SELECT $1 + $2;
$$
LANGUAGE SQL;
SELECT my_function(1, 2);
I would do this instead of a PREPARE since it will be simpler to update it. Depending on how complex the function is, you might want to also look at some of the other PL's in Postgres.
SQL procs are notoriously hard to debug. My lame but practical solution has been to write log messages to a log table, like this (please excuse syntax issues):
create table log_message (
log_timestamp timestamp not null default current_timestamp,
message varchar(1000)
);
then add lines to your stored proc like:
insert into log_message (message) values ("The value of x is " || #x);
Then after a run:
select * from log_message order by 1;
It's not pretty, but works in every DB.