I have a simple stored procedure to calculate the sum of salaries of employees, sum of their squares and number of rows.
This is the stored procedure I have written:
I get an error in fetching the number of rows from the database and assigning it to a variable. What do I do? Using DB2 11.5
It helps to specify the exact error code when asking questions (don't write get an error, do write instead 'get error SQL0104N ...`.
Your mistake is that you have not followed the documented order for SQL statements in compound SQL blocks.
The SELECT statement can only appear after any cursor definitions, local procedures , and handlers if you have any.
So move the statement SELECT COUNT(*) INTO TOTAL_ROWS FROM EMPLOYEE; so that it appears after the DECLARE CURSOR1 ... line, the try to recompile.
Related
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
i try to get an SP to run on DB2 connected with Squirrel
CREATE OR REPLACE PROCEDURE BOCA.TESTSP
(IN CASID INTEGER)
READS SQL DATA
DETERMINISTIC
LANGUAGE SQL
BEGIN
SELECT * FROM BOCA.TCASE C WHERE C.ID = CASID
END;
I get various errors based on where I put the ; (at end of statement etc)
i tried to follow this approach:
CREATE PROCEDURE [qualifier.]<procedure_name>
([IN | OUT | INOUT <argument_name> <datatype>,...])
{MODIFIES SQL DATA |
NO SQL |
CONTAINS SQL |
READS SQL DATA}
[[NOT] DETERMINISTIC]
LANGUAGE SQL
BEGIN [ATOMIC]
<procedure_body>
END
But did not succeed.
Anyone have a simple select that runs?
Stange is that an sample update I was able to create
Take some time to study the sample code that IBM supplies for SQL PL procedures, get these samples built and working in your environment. The samples are in the documentation, also they are on github, also they are in the SAMPLES directory of your Db2-server installation (for DB2 on Linux/Unix/Windows).
Your procedure has some mistakes:
missing statement separator after the SELECT statement
incorrect usage of SELECT in a routine. You either want to declare and open a cursor to return a result set to the caller or client, or you want to use SELECT...INTO to process the result of the query inside your routine.
missing valid separator at the end of the block of code (after the final END)
For SQuirrel SQL Client, before you connect to the database:
File > New Session Properties > SQL
(scroll down the list of properties until you see:
Statement Separator ;
Change the Statement Separator to #
Click OK.
Now connect to the database.
When you then type any SQL statement inside Squirrel (or a block, such as a trigger, stored-procedure, user defined function etc), you must now use the new statement separator instead of the previous default value at the end of the whole statement.
Inside of your routines , you will still need to use the semicolon to delimit statements inside the block, but remember to specify the new statement separator at the end of the block (after the final END in the stored procedure in your case).
My Agenda is to store the counts of the 2 tables ( being passed in the parameter ) and then do some more operations upon comparing the both.
PROBLEM -
Stored Procedure throwing Error :
CREATE OR REPLACE PROCEDURE dev.gp_count_matching_20191204(actual_tablename character varying(256), bkp_tablename character varying(256))
LANGUAGE plpgsql
AS $$
DECLARE
actual_table_name varchar(256);
backup_table_name varchar(256);
actual_count_query varchar(1024);
actual_count int;
backup_count_query varchar(1024);
backup_count int;
BEGIN
call dev.gp_test_error_handling_tablename_format(actual_tablename);
call dev.gp_test_error_handling_tablename_format(bkp_tablename);
actual_count:=(select count(*) as counts from actual_tablename);
--raise info 'Actual Table Name - %, Actual Table Count - %',actual_tablename,actual_count;
end;
$$
This throws the following Error while creating the stored procedure-
An error occurred when executing the SQL command:
CREATE OR REPLACE PROCEDURE dev.gp_count_matching_20191204(actual_tablename character varying(256), bkp_tablename character varying(256))
LANGUAGE pl...
[Amazon](500310) Invalid operation: syntax error at or near "$1";
1 statement failed.
Execution time: 0.99s
If I comment out the actual_count:=(select count(*) as counts from actual_tablename);
then the Stored Procedure gets created Successfully.
I guess it has something to do with me using the parameter ( since $1 points the first parameter ) in the query.
Since I am pretty new with Stored procedure, I unable to figure out the exact problem.
Thanks in Advance.
You need to use EXECUTE when running dynamic SQL. In your example the query is in parentheses but nothing is making it execute. To execute the query into a variable you using the INTO syntax
sql := 'SELECT …'
EXECUTE sql_var INTO result_var;
Please see the example Stored Procedures in our GitHub repo "Amazon Redshift Utils". https://github.com/awslabs/amazon-redshift-utils/tree/master/src/StoredProcedures
There are several examples that use dynamic SQL, such as https://github.com/awslabs/amazon-redshift-utils/blob/master/src/StoredProcedures/sp_split_table_by_range.sql
You can also use the below-given syntax:
SELECT INTO Count count(*) from table_name;
By doing this you are inserting the value of count(*) into variable Count. It works.
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;
/
I've created one of many stored procedures as part of an ETL process and one of the queries within a stored procedure isn't executing.
The environment is SQL Server 2012 SP2.
The bizarre thing is this -
Run the select part of the insert (affected query) - returns rows
Run the insert (affected query) - inserts rows
Run the whole stored procedure via SSMS - inserts rows
Run via SSIS - all other queries run barring the one of concern!
There are no conditions in my stored procedure e.g. if x = True the Return and no debug code is in there either e.g. return. There are also no transactions and the table I am reading from is a staging table populated prveiously by a data flow.
The query:
INSERT INTO Person.CustomerLinks
(PersonID, SystemID, CustomerID_bin, CustomerActive)
SELECT i.PersonID
, s.SystemDefID
, i.CustomerID_bin
, 0
FROM Staging.IdentifyOutput i
JOIN Config.SystemDef s ON s.OutputType = i.OutputType
WHERE i.CustomerID_bin IS NOT NULL
AND i.OutputType IN ('L', 'X')
AND i.PersonID > 0
AND i.FileDuplicate = 1
AND i.PreferredRecord = 1
AND NOT EXISTS ( SELECT 1
FROM Person.CustomerLinks cl
WHERE cl.PersonID = i.PersonID
AND cl.CustomerID_bin = i.CustomerID_bin)
The procedure has a Try Catch block and the Catch will raise an error and no error is raised.
The only other non ETL code in the procedure is -
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
But I put this in all of my procedures in this application as I am not concerned about dirty reads as they won't happen.
I placed tsql directly after the insert to write to my audit system and ##RowCount was 0. Yet if I run the select now I get over 1.5 million rows back.
Any suggestions?
If you are using a Execute T-SQL task then please try replacing it with the Execute SQL Task.
I don't know what caused it, but I moved the specific SQL into another stored procedure and it worked. In reality, it warranted being in its own stored procedure by right as it was only semi related to the procedure in question.