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

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.

Related

MSSQL Adding .ToString to datefields

i have some querys in my new SQL 2019 Database that connect to a table in a different database (MSSQL Server 2016) via linked servers.
But Everytime there is a date field in that query i get the error "The function or aggregat .ToString could not be found or the name is ambiguous".
I found that there is a hierarchy-ID-Function named "toString".
But i cant see why SQL should be using it when i dont call it to.
When i delete all Date fields from that query it executes without a problem.
SELECT * FROM [LinkedServerName].Databasename.integris.Tablename
EDIT:
I just noticed, that SQL somehow edits my text.
is what i wrote into the vieweditor.
is what the error shows me.
EDIT2: i set up the same linked server on an old SQL 2005 Server. Everything works fine. I guess because the 2005 server has no ToString() function... but im not sure.
I can construct a situation that would recreate the error, but I can't say for sure whether this is what is happening in your case. It's hard to demonstrate in a comment though, so I'll do as an answer.
Suppose we have a table t, with a computed column f, which references function dbo.f, which in turn references function dbo.g:
create function dbo.g() returns int as begin return 1 end;
go
create function dbo.f() returns int as begin return dbo.g(); end;
go
create table t(i int, f as dbo.f());
go
insert t(i) values (1);
select * from t;
go
So far so good. But now we drop function dbo.g and try to select from t:
drop function dbo.g;
select * from t;
This will result in the error: Cannot find either column "dbo" or the user-defined function or aggregate "dbo.g", or the name is ambiguous.
Look at the definition of the source table and check for computed columns, especially computed columns using nested constructs.

Can I display the result of dynamic SQL select statement in SQL Developer's grid?

In Oracle's SQL Developer, I can execute a "dynamic" SQL select statement in the Script Output pane with something like:
script
var tabName = 'all_users';
sqlcl.setStmt('select * from ' + tabName);
sqlcl.run();
/
Now, I am wondering if it is possible to execute a dynamic select statement such that its result is displayed in the result grid.
SqlDev is implemented in Java which includes Nashorn scripting engine. Therefore, you can basically execute JDBC in your Nashorn script. Here is one way to do it.
Paste
select REGIONS,
COUNTRIES,
LOCATIONS,
DEPARTMENTS,
JOBS,
EMPLOYEES,
JOB_HISTORY,
TM_USER_INFO,
USER_ROLES,
PAYMENT_PRICE_SHOP,
SHOP_USER from dual
into your worksheet.
Open "Code Outline" panel. Type "arbori", or open the following "querySQL.arbori" file:
include "std.arbori"
prelude: runOnce -> {
var ConnectionResolver = Java.type('oracle.dbtools.db.ConnectionResolver');
var connectionList = ConnectionResolver.getConnectionNames();
var conn = ConnectionResolver.getConnection('IdeConnections%23local_hr');
}
queries: [node) column -> {
var node = tuple.get('node');
var table = target.input.substring(
target.src[node.from].begin,
target.src[node.to-1].end
);
var query = 'select * from ' + table;
var ps = conn.createStatement();
var rs = ps.executeQuery(query);
while( rs.next() )
print(rs.getString(1));
}
It outputs the first column of each table to the standard java output
, so it would require some reverse engineering to get a handle to the SqlDev script output panel and channel the result there.
Dynamic SQL can be displayed in an Oracle SQL Developer grid using a function returning a ref cursor (sort of), a polymorphic table function (18c+), or Oracle Data Cartridge (requires custom PL/SQL packages and types).
Function Returning Ref Cursor
As explained in this answer, the output from a function returning a ref cursor can be displayed in the "Output Variables" window. The example from the answer used static SQL, but it's pretty easy to make it dynamic:
create or replace function refcursor_function return sys_refcursor as
c sys_refcursor;
begin
open c for 'select * from all_objects';
return c;
end;
/
The downside is that getting the results takes a few more clicks than a normal query, and the Output Variables grid is not nearly as powerful as the regular results grid. If you just need a window for viewing and copying and pasting, Output Variables is good enough. But it doesn't allow any advanced GUI features.
Polymorphic Table Function
Since Oracle 18c, you can create a polymorphic table function that accepts input and has variable output. You have to program how to handle the tables and columns, but if you just need simple pass-through logic it's not that difficult. See my answer here for an example of a query that returns every column from a table excluding specific columns. The results are "regular" SQL as far as any program knows, and will work in any grid GUI.
Oracle Data Cartridge
My open source program Method4 can run dynamic SQL in SQL. After you download and install the packages and types, you can write a SQL statement that generates another SQL statement. If you need to use PL/SQL to generate the query, you may need to use a PL/SQL WITH function. Like the polymorphic table function, the results look like normal SQL and will work in any grid.
select * from table(method4.dynamic_query(
q'[
--Query that builds another query.
select replace(
q'!
select * from #TABLE_NAME#
!', '#TABLE_NAME#', table_name) sql_statement
from
(
--Enter your script here that returns a table name to select from.
select 'ALL_USERS' table_name
from dual
)
]'
));
You may want to add some details about what exactly you're trying to do and why; that might help narrow down the possible solutions.

DB2: How to get generated always as statement to work with session user

I need to get a userstamp into a table and have not managed to figure out how the GENERATED FOR EACH ROW ON UPDATE AS statement works with the SESSION_USER variable in DB2 10.5 (LUW).
Managed to get an implementation working using a function which has a fake variable for forcing the evaluation in update statements:
CREATE OR REPLACE FUNCTION XXX.CURRENT_USER( tmp varchar(128))
SPECIFIC xxx.XXX_CURRENT_USER
RETURNS VARCHAR(128)
CONTAINS SQL DETERMINISTIC NO EXTERNAL ACTION
BEGIN
RETURN session_user ;
END
GO
CREATE TABLE xxx (
i INTEGER,
t VARCHAR(128) GENERATED ALWAYS AS (XXX.CURRENT_USER(i))
)
However, would be nice have less "hacky" implementation for a basic thing like this.
For the time stamps there is that "FOR EACH ROW ON UPDATE AS ROW CHANGE TIMESTAMP" statement but no equivalent for other register variables it seems.
Help is very much appreciated
Does this work?
CREATE TABLE xxx (
i INTEGER,
t VARCHAR(128) WITH DEFAULT session_user
);
I don't have DB2 on hand to check, but this is very similar to the syntax used in other databases (although the more typical syntax does not use WITH).

Error SQL0104 when creating a function in System i V7R1

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 |

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.