DB2 Stored Procedure: Declare an internal "rowset" - stored-procedures

I'm migrating some procedures from PostgreSQL to a new DB2 environment. I've got most of it done but I can't find a way to DECLARE a variable for an internal rowset/record.
Basically what the procedure does on Postgres is this:
DECLARE
counts RECORD;
BEGIN
-- fill "counts" with one row of aggregated data
SELECT
COUNT(....) AS failed_inserts,
COUNT(....) AS failed_updates,
COUNT(....) AS failed_deletes,
INTO counts
FROM (...)
-- check "counts" with some conditionals
IF counts.failed_inserts > 0
(...)
END IF;
(...)
-- return info depending on the data
RETURN (...);
END
I can't find an equivalent to declaring "counts" in the IBM manuals or elsewhere online. The row I need is static (3 columns of aggregated data). So it would be enough to declare that row hardcoded if that is possible.
Is it possible to DECLARE a record / dataset / "virtual table" within a Stored Procedure on the DB2?
We're using DB2 for Linux (V10.5) not DB2 for iSeries.
#mustaccio's answer points to the correct solution:
Outside of the procedure create the needed rowtype:
CREATE TYPE empRow AS ROW (failed_inserts INTEGER, failed_updates INTEGER, failed_deletes INTEGER);
Then you can DECLARE the new type within the procedure
DECLARE newRow empRow;

Not sure I fully understand what you want, but may be you're looking for the ROW data type? Something like this:
DECLARE
TYPE counts_row AS ROW (
failed_inserts INT,
failed_updates INT,
failed_deletes INT
);
counts counts_row;
BEGIN
-- fill "counts" with one row of aggregated data
SELECT
COUNT(....) AS failed_inserts,
COUNT(....) AS failed_updates,
COUNT(....) AS failed_deletes,
INTO counts
FROM (...);
...
PS. Not tested.
More info in the manual.

In lieu of creating a permanent** user defined type that is more or less specific to a single query, you can also achieve the same by using the FOR statement:
FOR counts AS c1 CURSOR FOR SELECT COUNT(.....) AS failed_inserts,
COUNT(....) AS failed_updates,
COUNT(....) AS failed_deletes,
FROM (...)
DO
IF counts.failed_inserts > 0 THEN
(....)
END IF;
END FOR;
** Permanent meaning something that's defined in the system catalog.

Related

Bigquery - parametrize tables and columns in a stored procedure

Consider an enterprise that captures sensor data for different production facilities. per facility, we create an aggregation query that averages the values to 5min timeslots. This query exists out of a long list of with-clauses and writes data to a table (called aggregation_table).
Now my problem: currently we have n queries running that exactly run the same logic, the only thing that differs are table names (and sometimes column names but let's ignore that for now).
Instead of managing n different scripts that are basically the same, I would like to put it in a stored procedure that is able to work like this:
CALL aggregation_query(facility_name) -> resolve the different tables for that facility and then use them in the different with clauses
On top of that, instead of having this long set of clauses that give me the end-result, I would like to chunk them up in logical blocks that are parametrizable, So for example, if I call the aforementioned stored_procedure for facility A, I want to be able to pass / use this table name in these different functions, where the output can be re-used in the next statement (like you would do with with clauses).
Another argument of why I want to chunk this up in re-usable blocks is because we have many "derivatives" on this aggregation query, for example to manage historical data, to correct data or to have the sensor data on another aggregation level. As these become overly complex, it is much easier to manage them without having to copy paste and adjust these every time.
In the current set-up, it could be useful to know that I am only entitled to use plain BigQuery, As my team is not allowed to access the CI/CD / scheduling and repository. (meaning that I cannot solve the issue by having CI/CD that deploys the n different versions of the procedure and functions)
So in the end, I would like to end up with something like this using only bigquery:
CREATE OR REPLACE PROCEDURE
`aggregation_function`()
BEGIN
DECLARE
tablename STRING;
DECLARE
active_table_name STRING; ##get list OF tables CREATE TEMP TABLE tableNames AS
SELECT
table_catalog,
table_schema,
table_name
FROM
`catalog.schema.INFORMATION_SCHEMA.TABLES`
WHERE
table_name = tablename;
WHILE
(
SELECT
COUNT(*)
FROM
tableNames) >= 1 DO ##build dataset + TABLE name
SET
active_table_name = CONCAT('`',table_catalog,'.',table_schema,'.' ,table_name,'`'); ##use concat TO build string AND execute
EXECUTE IMMEDIATE '''
INSERT INTO
`aggregation_table_for_facility` (timeslot, sensor_name, AVG_VALUE )
WITH
STEP_1 AS (
SELECT
*
FROM
my_table_function_step_1(active_table_name,
parameter1,
parameter2) ),
STEP_2 AS (
SELECT
*
FROM
my_table_function_step_2(STEP_1,
parameter1,
parameter2) )
SELECT * FROM STEP_2
'''
USING active_table_name as active_table_name;
DELETE
FROM
tableNames
WHERE
table_name = tablename;
END WHILE
;
END
;
I was hoping someone could make a snippet on how I can do this in Standard SQL / Bigquery, so basically:
stored procedure that takes in a string variable and is able to use that as a table (partly solved in the approach above, but not sure if there are better ways)
(table) function that is able to take this table_name parameter as well and return back a table that can be used in the next with clause (or alternatively writes to a temp table)
I think below code snippets should provide you with some insights when dealing with procedures, inserts and execute immediate statements.
Here I'm creating a procedure which will insert values into a table that exists on the information schema. Also, as a value I want to return I use OUT active_table_name to return the value I assigned inside the procedure.
CREATE OR REPLACE PROCEDURE `project-id.dataset`.custom_function(tablename STRING,OUT active_table_name STRING)
BEGIN
DECLARE query STRING;
SET active_table_name= (SELECT CONCAT('`',table_catalog,'.',table_schema,'.' ,table_name,'`')
FROM `project-id.dataset.INFORMATION_SCHEMA.TABLES`
WHERE table_name = tablename);
#multine query can be handled by using ''' or """
Set query =
'''
insert into %s (string_field_0,string_field_1,string_field_2,string_field_3,string_field_4,int64_field_5)
with custom_query as (
select string_field_0,string_field_2,'169 BestCity',string_field_3,string_field_4,55677 from %s limit 1
)
select * from custom_query;
''';
# querys must perform operations and must be the last thing to perform
# pass parameters using format
execute immediate (format(query,active_table_name,active_table_name));
END
You can also use a loop to iterate trough records from a working table so it will execute the procedure and also be able to get the value from the procedure to use somewhere else.ie:A second procedure to perform a delete operation.
DECLARE tablename STRING;
DECLARE out_value STRING;
FOR record IN
(SELECT tablename from `my-project-id.dataset.table`)
DO
SET tablename = record.tablename;
LOOP
call `project-id.dataset`.custom_function(tablename,out_value);
select out_value;
END LOOP;
END FOR;
To recap, there are some restrictions such as the possibility to call procedures inside a execute immediate or to use execute immediate inside an execute immediate, to count a few. I think these snippets should help you dealing with your current situation.
For this sample I use the following documentation:
Data Manipulation Language
Dealing with outputs
Information Schema Tables
Execute Immediate
For...In
Loops

Return 2 resultset from cursor based on one query (nested cursor)

I'm trying to obtain 2 different resultset from stored procedure, based on a single query. What I'm trying to do is that:
1.) return query result into OUT cursor;
2.) from this cursor results, get all longest values in each column and return that as second OUT
resultset.
I'm trying to avoid doing same thing twice with this - get data and after that get longest column values of that same data. I'm not sure If this is even possible, but If It is, can somebody show me HOW ?
This is an example of what I want to do (just for illustration):
CREATE OR REPLACE PROCEDURE MySchema.Test(RESULT OUT SYS_REFCURSOR,MAX_RESULT OUT SYS_REFCURSOR)
AS
BEGIN
OPEN RESULT FOR SELECT Name,Surname FROM MyTable;
OPEN MAX_RESULT FOR SELECT Max(length(Name)),Max(length(Surname)) FROM RESULT; --error here
END Test;
This example compiles with "ORA-00942: table or view does not exist".
I know It's a silly example, but I've been investigating and testing all sorts of things (implicit cursors, fetching cursors, nested cursors, etc.) and found nothing that would help me, specially when working with stored procedure returning multiple resultsets.
My overall goal with this is to shorten data export time for Excel. Currently I have to run same query twice - once for calculating data size to autofit Excel columns, and then for writing data into Excel.
I believe that manipulating first resultset in order to get second one would be much faster - with less DB cycles made.
I'm using Oracle 11g, Any help much appreciated.
Each row of data from a cursor can be read exactly once; once the next row (or set of rows) is read from the cursor then the previous row (or set of rows) cannot be returned to and the cursor cannot be re-used. So what you are asking is impossible as if you read the cursor to find the maximum values (ignoring that you can't use a cursor as a source in a SELECT statement but, instead, you could read it using a PL/SQL loop) then the cursor's rows would have been "used up" and the cursor closed so it could not be read from when it is returned from the procedure.
You would need to use two separate queries:
CREATE PROCEDURE MySchema.Test(
RESULT OUT SYS_REFCURSOR,
MAX_RESULT OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN RESULT FOR
SELECT Name,
Surname
FROM MyTable;
OPEN MAX_RESULT FOR
SELECT MAX(LENGTH(Name)) AS max_name_length,
MAX(LENGTH(Surname)) AS max_surname_length
FROM MyTable;
END Test;
/
Just for theoretical purposes, it is possible to only read from the table once if you bulk collect the data into a collection then select from a table-collection expression (however, it is going to be more complicated to code/maintain and is going to require that the rows from the table are stored in memory [which your DBA might not appreciate if the table is large] and may not be more performant than compared to just querying the table twice as you'll end up with three SELECT statements instead of two).
Something like:
CREATE TYPE test_obj IS OBJECT(
name VARCHAR2(50),
surname VARCHAR2(50)
);
CREATE TYPE test_obj_table IS TABLE OF test_obj;
CREATE PROCEDURE MySchema.Test(
RESULT OUT SYS_REFCURSOR,
MAX_RESULT OUT SYS_REFCURSOR
)
AS
t_names test_obj_table;
BEGIN
SELECT Name,
Surname
BULK COLLECT INTO t_names
FROM MyTable;
OPEN RESULT FOR
SELECT * FROM TABLE( t_names );
OPEN MAX_RESULT FOR
SELECT MAX(LENGTH(Name)) AS max_name_length,
MAX(LENGTH(Surname)) AS max_surname_length
FROM TABLE( t_names );
END Test;
/

Is it possible to pass in a variable amount of parameters to a stored procedure in redshift?

I am trying to write a stored procedure in AWS Redshift SQL and one of my parameters needs the possibility to have an integer list (will be using 'IN(0,100,200,...)' inside there WHERE clause). How would I write the input parameter in the header of the procedure so that this is possible (if at all?)
I've tried passing them in as a VARCHAR "integer list" type thing but wasn't sure then how to parse that back into ints.
Update: I found a way to parse the string and loop through it using the SPLIT_PART function and store all of those into a table. Then just use a SELECT * FROM table with the IN() call
What I ended up doing was as follows. I took in the integers that I was expecting as a comma-separated string. I then ran the following on it.
CREATE OR REPLACE PROCEDURE test_string_to_int(VARCHAR)
AS $$
DECLARE
split_me ALIAS FOR $1;
loop_var INT;
BEGIN
DROP TABLE IF EXISTS int_list;
CREATE TEMPORARY TABLE int_list (
integer_to_store INT
);
FOR loop_var IN 1..(REGEXP_COUNT(split_me,',') + 1) LOOP
INSERT INTO int_list VALUES (CAST(SPLIT_PART(split_me,',',loop_var) AS INT));
END LOOP;
END;
$$ LANGUAGE plpgsql;
So I would call the procedure with something like:
CALL test_string_to_int('1,2,3');
and could do a select statement on it to see all the values stored into the table. Then in my queries the need this parameter I ran:
.........................
WHERE num_items IN(SELECT integer_to_store FROM int_list);

provide column data type in DBSMS_SQL.DEFINE_COLUMN oracle 12C

I have to copy data from one table to another with below two conditions
table names will be known at run time
records need to be copied one at a time so that modifications can be done in column values when required
I have created a procedure to to do this through dynamic query. Since the column list is not known already I am not able to declare a rowtype variable. I saw an example of DBMS_SQL where you can define the columns for select clause. Below is the format
DBMS_SQL.DEFINE_COLUMN(cursor_var,position,column_var);
Problem here is that in all the examples I found the column_var were already declared. However in my case I will get to know the no of columns that will be in cursor sql and their data type at run time. so I need to find a way to pass the data type of "column_var" as part of DBMS_SQL.DEFINE_COLUMN. Is there a way to do that? Is there a better way?
Below is just a sample code
CREATE OR REPLACE PROCEDURE pr_test (P_TABLE_NAME IN VARCHAR2)
IS
V_SQL VARCHAR2(500);
SRC_CUR INT;
DEST_CUR INT;
TYPE COL_DTL_TYPE IS RECORD
(
COLUMN_ID INT,
COLUMN_NAME VARCHAR2(250),
DATA_TYPE VARCHAR2(250),
DATA_LENGTH INT
);
COL_DTL_REC COL_DTL_TYPE;
TYPE TBL_COL_LIST_TYPE IS TABLE OF COL_DTL_TYPE;
TBL_COL_LIST TBL_COL_LIST_TYPE;
V_CNT INT := 0;
BEGIN
V_SQL := 'SELECT * FROM ' || P_TABLE_NAME;
SRC_CUR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SRC_CUR,V_SQL,DBMS_SQL.NATIVE);
TBL_COL_LIST := TBL_COL_LIST_TYPE();
FOR COL_DTL_REC IN (
SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH
FROM ALL_TAB_COLUMNS WHERE TABLE_NAME =P_TABLE_NAME
)
LOOP
V_CNT := V_CNT + 1;
TBL_COL_LIST.EXTEND;
TBL_COL_LIST(V_CNT) := COL_DTL_REC;
-- Here is where I am stuck and not able to give column data type
DBMS_SQL.DEFINE_COLUMN(SRC_CUR,V_CNT,COL_DTL_REC.COLUMN_NAME COL_DTL_REC.DATA_TYPE , COL_DTL_REC.DATA_LENGTH)
END LOOP;
END;
copying to destination table will come later.

Postgres function returns custom data set

Is there a way to create postgres stored function (using plpgsql to be able to set input parameters) that returns a custom data set?
I've tried to do something like this according to official manual:
CREATE FUNCTION extended_sales(p_itemno int)
RETURNS TABLE(quantity int, total numeric) AS $$
BEGIN
RETURN QUERY SELECT quantity, quantity * price FROM sales
WHERE itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;
but result is an array with only one column which contains type (quantity, total), but I need to get two column array with 'quantity' column and 'total' column.
At a guess you're running:
SELECT extended_sales(1);
This will return a composite type column. If you want it expanded, you must instead run:
SELECT * FROM extended_sales(1);
Also, as #a_horse_with_no_name notes, a PL/pgSQL function is completely unnecessary here. Presumably this is a simplified example?
In future please include:
Your PostgreSQL version; and
The exact SQL you ran and the exact output you got

Resources