Using User defined types in DB2 - db2

I am migrating my database from ORACLE to DB2, While doing the migrations I have come across a
situation related to User defined types. As I have been working with DB2 recently, I have been through the SQL standards and have been successful in migrating most of the stuffs.
I am kind of stuck with this UDT and tried with few of the suggestion by some DB experts here, but the thing I am trying to achieve isnt working well.
I have created the below UDT and created another UDT as table and been trying to use in a function.
CREATE OR REPLACE TYPE "XYZ" AS OBJECT (
aa VARCHAR2(30),
bb NUMBER (18),
cc NUMBER (30, 12),
count NUMBER (18),
value_1 NUMBER (18),
value_2 NUMBER (18),
value_3 NUMBER (18),
display_1 VARCHAR2 (256),
display_2 VARCHAR2 (256),
display_3 VARCHAR2 (256),
status NUMBER (1)
)
CREATE OR REPLACE TYPE "XYZ_TBL" AS TABLE OF XYZ;
As I have read that If we create a UDT in DB2, DB2 automatically creates a function of the above XYZ without parameters I believe. And to use the UDT, we have to create a function calling the constructor. I created a function with a same name XYZ referencing the UDT.
CREATE OR REPLACE FUNCTION XYZ(
aa VARCHAR2,
bb NUMBER,
cc NUMBER,
count NUMBER,
value_1 NUMBER,
value_2 NUMBER,
value_3 NUMBER,
display_1 VARCHAR2,
display_2 VARCHAR2,
display_3 VARCHAR2,
status NUMBER
)
return XYZ
IS
rec xyz := xyz(cast(null as varchar), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as varchar), cast(null as varchar), cast(NULL as varchar),cast(null as decimal));
BEGIN
RETURN rec;
END;
Every thing got compiled perfectly . And I am calling this function in a package as
FUNCTION get_combinations(aa VARCHAR2, bb VARCHAR2,
cc VARCHAR, dd NUMBER,
prefix_1 VARCHAR2, prefix_2 VARCHAR2,
prefix_3 VARCHAR2, filter_1 NUMBER,
filter_2 NUMBER, filter_3 NUMBER)
RETURN xyz_tbl PIPELINED
IS
rec xyz := xyz(cast(null as varchar), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as decimal), cast(null as varchar), cast(null as varchar), cast(NULL as varchar),cast(null as decimal));
cur sys_refcursor;
ccyCol VARCHAR(64);
txQuery VARCHAR(4000);
\\and doing further stuffs
Now, When I am using the function in one of the stored procedure, It gives me the below error
SQL0724N The activation of "XYZ" of type "FUNCTION " would exceed the maximum level of indirect SQL cascading.
and when I try to use the UDT XYZ directly into the package without creating any caller for it, the package is not able to locate XYZ and doesn't compile.
In a package I am using the function XYZ to pass the values from different set of tables and building a dynamic select query for further processing.
May be I am doing something wrong against the SQL standards?

The constructor function for a structured type is created automatically by Db2. It returns a new object of the type with all fields set to NULL. You would then need to create setter methods to set the fields appropriately.
The "constructor" that you have defined calls itself recursively until it exceeds the maximum recursion level of 64.

Related

What is the format for an `INTERVAL` parameter in a `format()` call

I'm updating an existing stored function, handling two additional parameters and inserting them, where required. Within the function, I INSERT a row into a table using a call to EXECUTE format(), something like this...
CREATE OR REPLACE PROCEDURE function_p (
p_name TEXT DEFAULT '',
p_step INT DEFAULT NULL,
p_project_duration INTERVAL DEFAULT '2W',
)
LANGUAGE plpgsql AS $$
BEGIN
EXECUTE format('
INSERT INTO table_2 (column_1, column_2, column_3, name, step)
SELECT one_column, two_column, three_column, %L, %s
FROM generate_series(now()::DATE, now()::DATE + %L, INTERVAL ''1 day'') d
CROSS JOIN table_1',
p_name, p_step, p_project_duration);
END;
$$
I know this is a pretty rubbish example, but it's pseudo-code, ok?! ;)
In the generate_series() call...
If I use format %L I get error "operator is not unique: date + unknown"
If I use format %I I get error "column "P14d" does not exist"
If I use format %s I get error "column "P14d" does not exist"
Don't pass strings as parameters, use placeholders and pass the correct data types. Only use the format() placeholders for the identifiers, pass the actual parameters with the using clause of the execute command:
CREATE OR REPLACE PROCEDURE function_p (
p_name TEXT DEFAULT '',
p_step INT DEFAULT NULL,
p_project_duration INTERVAL DEFAULT '2W',
)
LANGUAGE plpgsql AS $$
BEGIN
EXECUTE format('
INSERT INTO table_2 (column_1, column_2, column_3, name, step)
SELECT one_column, two_column, three_column, %L, %s
FROM generate_series($1, $2, $3) d
CROSS JOIN table_1', p_name, p_step)
using current_date, current_date + p_project_duration, interval '1 day';
END;
$$
It seems I've found a hack-fix/cludge/bodge, yay!
It works if I use the '%s' format and then cast it to INTERVAL.
FROM generate_series(now()::DATE, now()::DATE + ''%s''::INTERVAL, INTERVAL ''1 day'') d

COALESCE doesn't seem to fire in Postgres 12 SQL stored function

Using Postgres 12.x, I've just run into a weird problem. For some reason, I'm not getting COALESCE to work in a stored SQL function. Meaning, I get NULL instead of my first non-null value.
I suspect that I've missed something obvioius, but can't see it. Can anyone spot what the problem is? I've built a stand-alone example that reproduces the issue.
Thanks
Test Setup
BEGIN;
------------------------------------
-- Define table
------------------------------------
DROP TABLE IF EXISTS lookup_table CASCADE;
CREATE TABLE IF NOT EXISTS lookup_table (
id uuid NOT NULL DEFAULT NULL, -- Values are geneated by an outside system.
name_ citext NOT NULL DEFAULT NULL
);
------------------------------------
-- Seed
------------------------------------
INSERT INTO lookup_table (id, name_)
VALUES ('6ea225f3-9819-49eb-bdb8-f3ae23af6337', 'John'),
('ae2282c6-ca9b-413f-b182-a1ca69fc3c78', 'Alice'),
('7ee1ce1b-4efc-426c-a6f8-5b2375cb357e', 'Magrite'),
('71c3d96a-3ac7-454e-bfff-e5551428c017', 'Arjun');
------------------------------------
-- Define the lookup fnction
------------------------------------
CREATE OR REPLACE FUNCTION lookup_function (id_in uuid)
RETURNS citext AS
$BODY$
-- For some reason, COALESCE doesn't work here, but does outside of a function. Weird.
-- Tried with CTE, same same.
SELECT COALESCE (name_ , '') FROM lookup_table WHERE id = id_in;
$BODY$
LANGUAGE SQL;
------------------------------------
-- Test it out
------------------------------------
SELECT 'Alice' AS description,
lookup_function ('ae2282c6-ca9b-413f-b182-a1ca69fc3c78') AS result
UNION ALL
SELECT 'Should return an empty string, returns NULL instead' AS description,
lookup_function ('00000000-0000-0000-0000-000000000000') AS result
Standard Call
For comparison, here's a standard call right in a SELECT that works fine:
select coalesce (name_, '')
from lookup_table
where id = '00000000-0000-0000-0000-000000000000';
-- Returns an empty string, not NULL.
But, in a SQL function, this expression returns NULL instead.
CTE Attempt
Here's my stab at using a CTE to help out in the function, but I still get back NULL.
------------------------------------
-- Define the lookup fnction
------------------------------------
CREATE OR REPLACE FUNCTION lookup_function (id_in uuid)
RETURNS citext AS
$BODY$
WITH lookup_result AS (
select name_ from lookup_table where id = id_in
)
SELECT CASE WHEN name_ IS NULL THEN ''
ELSE name_ END
FROM lookup_result
$BODY$
LANGUAGE SQL;
The lookup function doesn't return anything from the select statement inside it as the where clause fails. The coalesce is never executed therefore.
A null is returned by default.
You can rewrite the select as:
select coalesce((SELECT name_ FROM lookup_table WHERE id = id_in),'');
The coalesce will turn NULL value for name_ into the empty string. But you don't have a NULL value for that column, you have zero rows. This gets converted to NULL at a later stage, where the coalesce can't get its hands on it.
Maybe you want this:
CREATE OR REPLACE FUNCTION lookup_function (id_in uuid)
RETURNS citext AS
$BODY$
SELECT COALESCE (name_ , '') FROM (values (id_in))f(x) left join lookup_table on id = x;
$BODY$
LANGUAGE SQL;

Return both out value and query select value in postgres

Here is my sql server sample procedure while migrating to postgres sql i am unable to return output value and
return query value.I want to return SUCCESS and followed by query value
ALTER PROCEDURE [dbo].[VERIFY](
#P_VER_NO VARCHAR(5) ,
#P_SOLID VARCHAR(6) ,
#P_ID VARCHAR(7) ,
#P_MOBNO VARCHAR(15) ,
#P_OUT_MSG VARCHAR(2) OUT
)
AS
BEGIN
DECLARE #VOTP INT , #V_LNK INT ,#V_VERAVL VARCHAR(2)
IF #VOTP=0
BEGIN
SELECT #P_OUT_MSG='SUCCESS'
SELECT #P_OUT_MSG
SELECT MCI.CHQ_MIN_LGTH AS MINCHEQUELENGTH,
MCI.VRTL_CARD AS VIRTUALCARD,
FROM TRAN_TABLE MCI WITH(NOLOCK)
WHERE MCI_SOLID=#P_SOLID;
RETURN
END
ELSE
begin
SELECT #P_OUT_MSG='FAILURE'
SELECT #P_OUT_MSG
SELECT MCI.ACCT_STAT AS MINCHEQUELENGTH,
MCI.LOG_ATMPT AS VIRTUALCARD,
FROM TRAN_TABLE MCI WITH(NOLOCK)
WHERE MCI_SOLID=#P_SOLID;
RETURN
end
END
You should have two out argument one for success/failure, and other for the value.
Or you should not have an out argument for success/failure, but RAISE ESCEPTION on failure...

Pass table to postgreSQL function, execute select statement, return table

I'm transitioning to postgreSQL from MSSQL and still working out the syntax for procedural programming. The idea is to create a function that will take a table as an input/parameter, execute a series of SELECT statements against this input table (no temp tables; the SELECT statements will be executed with CTEs), and RETURN a final table.
The input table would be very straightforward:
Col_1 Col_2 Col_3
----------------------------
2 5 12
I want to use each of the fields in the input table to calculate a number (in this example, summing Col_1, Col_2, and Col_3), append it to the input table, and create an output table that appears as such:
Col 1 Col_2 Col_3 Col_4
---------------------------------------
2 5 12 19
My attempt at the code:
CREATE OR REPLACE FUNCTION summing_function(input_table)
RETURNS output_table(Col_1 INT, Col_2 INT, Col_3 INT, Col_4 INT) AS
$$
SELECT
i.*
, i."Col_1" + i."Col_2" + i."Col_3" as sum
INTO output_table
FROM input_table i
$$
LANGUAGE SQL;
Obviously everything between the dollar quotes is incomplete/wrong. What's the correct postgreSQL syntax for piece between the dollar quotes?
You can not pass a "table" as a function argument. You can pass the name of a table to a PL/pgSQL function and then run a dynamic query:
CREATE OR REPLACE FUNCTION summing_function(input_table text)
RETURNS TABLE (col_1 int, col_2 int, col_3 int, col_4 int) AS $$
BEGIN
RETURN QUERY EXECUTE
format('SELECT *, col_1 + col_2 + col_3 FROM %I', input_table);
END;
$$ LANGUAGE plpgsql;

Execute a dynamic crosstab query

I implemented this function in my Postgres database: http://www.cureffi.org/2013/03/19/automatically-creating-pivot-table-column-names-in-postgresql/
Here's the function:
create or replace function xtab (tablename varchar, rowc varchar, colc varchar, cellc varchar, celldatatype varchar) returns varchar language plpgsql as $$
declare
dynsql1 varchar;
dynsql2 varchar;
columnlist varchar;
begin
-- 1. retrieve list of column names.
dynsql1 = 'select string_agg(distinct '||colc||'||'' '||celldatatype||''','','' order by '||colc||'||'' '||celldatatype||''') from '||tablename||';';
execute dynsql1 into columnlist;
-- 2. set up the crosstab query
dynsql2 = 'select * from crosstab (
''select '||rowc||','||colc||','||cellc||' from '||tablename||' group by 1,2 order by 1,2'',
''select distinct '||colc||' from '||tablename||' order by 1''
)
as ct (
'||rowc||' varchar,'||columnlist||'
);';
return dynsql2;
end
$$;
So now I can call the function:
select xtab('globalpayments','month','currency','(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)','text');
Which returns (because the return type of the function is varchar):
select * from crosstab (
'select month,currency,(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)
from globalpayments
group by 1,2
order by 1,2'
, 'select distinct currency
from globalpayments
order by 1'
) as ct ( month varchar,CAD text,EUR text,GBP text,USD text );
How can I get this function to not only generate the code for the dynamic crosstab, but also execute the result? I.e., the result when I manually copy/paste/execute is this. But I want it to execute without that extra step: the function shall assemble the dynamic query and execute it:
Edit 1
This function comes close, but I need it to return more than just the first column of the first record
Taken from: Are there any way to execute a query inside the string value (like eval) in PostgreSQL?
create or replace function eval( sql text ) returns text as $$
declare
as_txt text;
begin
if sql is null then return null ; end if ;
execute sql into as_txt ;
return as_txt ;
end;
$$ language plpgsql
usage: select * from eval($$select * from analytics limit 1$$)
However it just returns the first column of the first record :
eval
----
2015
when the actual result looks like this:
Year, Month, Date, TPV_USD
---- ----- ------ --------
2016, 3, 2016-03-31, 100000
What you ask for is impossible. SQL is a strictly typed language. PostgreSQL functions need to declare a return type (RETURNS ..) at the time of creation.
A limited way around this is with polymorphic functions. If you can provide the return type at the time of the function call. But that's not evident from your question.
Refactor a PL/pgSQL function to return the output of various SELECT queries
You can return a completely dynamic result with anonymous records. But then you are required to provide a column definition list with every call. And how do you know about the returned columns? Catch 22.
There are various workarounds, depending on what you need or can work with. Since all your data columns seem to share the same data type, I suggest to return an array: text[]. Or you could return a document type like hstore or json. Related:
Dynamic alternative to pivot with CASE and GROUP BY
Dynamically convert hstore keys into columns for an unknown set of keys
But it might be simpler to just use two calls: 1: Let Postgres build the query. 2: Execute and retrieve returned rows.
Selecting multiple max() values using a single SQL statement
I would not use the function from Eric Minikel as presented in your question at all. It is not safe against SQL injection by way of maliciously malformed identifiers. Use format() to build query strings unless you are running an outdated version older than Postgres 9.1.
A shorter and cleaner implementation could look like this:
CREATE OR REPLACE FUNCTION xtab(_tbl regclass, _row text, _cat text
, _expr text -- still vulnerable to SQL injection!
, _type regtype)
RETURNS text
LANGUAGE plpgsql AS
$func$
DECLARE
_cat_list text;
_col_list text;
BEGIN
-- generate categories for xtab param and col definition list
EXECUTE format(
$$SELECT string_agg(quote_literal(x.cat), '), (')
, string_agg(quote_ident (x.cat), %L)
FROM (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
, ' ' || _type || ', ', _cat, _tbl)
INTO _cat_list, _col_list;
-- generate query string
RETURN format(
'SELECT * FROM crosstab(
$q$SELECT %I, %I, %s
FROM %I
GROUP BY 1, 2 -- only works if the 3rd column is an aggregate expression
ORDER BY 1, 2$q$
, $c$VALUES (%5$s)$c$
) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr -- expr must be an aggregate expression!
, _tbl, _cat_list, _col_list, _type);
END
$func$;
Same function call as your original version. The function crosstab() is provided by the additional module tablefunc which has to be installed. Basics:
PostgreSQL Crosstab Query
This handles column and table names safely. Note the use of object identifier types regclass and regtype. Also works for schema-qualified names.
Table name as a PostgreSQL function parameter
However, it is not completely safe while you pass a string to be executed as expression (_expr - cellc in your original query). This kind of input is inherently unsafe against SQL injection and should never be exposed to the general public.
SQL injection in Postgres functions vs prepared queries
Scans the table only once for both lists of categories and should be a bit faster.
Still can't return completely dynamic row types since that's strictly not possible.
Not quite impossible, you can still execute it (from a query execute the string and return SETOF RECORD.
Then you have to specify the return record format. The reason in this case is that the planner needs to know the return format before it can make certain decisions (materialization comes to mind).
So in this case you would EXECUTE the query, return the rows and return SETOF RECORD.
For example, we could do something like this with a wrapper function but the same logic could be folded into your function:
CREATE OR REPLACE FUNCTION crosstab_wrapper
(tablename varchar, rowc varchar, colc varchar,
cellc varchar, celldatatype varchar)
returns setof record language plpgsql as $$
DECLARE outrow record;
BEGIN
FOR outrow IN EXECUTE xtab($1, $2, $3, $4, $5)
LOOP
RETURN NEXT outrow
END LOOP;
END;
$$;
Then you supply the record structure on calling the function just like you do with crosstab.
Then when you all the query you would have to supply a record structure (as (col1 type, col2 type, etc) like you do with connectby.