Dynamically Populating an Oracle Cursor in a Stored Procedure - database

I need to dynamically populate an Oracle cursor (Oracle 10g). The SQL statement changes, based off of the input value to pull from different tables, and columns. What I don't want to do is have to maintain a temporary table that I truncate and load everytime the sproc is executed. Here is what I am currently doing, but if there is another alternative I would appreciate the help:
Stored Procedure
PROCEDURE Get_Type_One_Polygon_Values(in_role VARCHAR2, rc_generic OUT SYS_REFCURSOR) as
BEGIN
execute immediate 'truncate table teamchk.temp_type_one_roles';
execute immediate 'INSERT INTO TEAMCHK.TEMP_TYPE_ONE_ROLES ' ||
'SELECT ' || in_role || '_POLY_ID, ' || in_role || '_POLY_NAME ' ||
'FROM TEAMCHK.' || in_role;
open rc_generic for
select * from teamchk.temp_type_one_roles;
END;
Temp Table
CREATE TABLE TEAMCHK.TEMP_TYPE_ONE_ROLES
(
ROLE_ID NUMERIC(38,0),
ROLE_NAME VARCHAR2(75)
);

That's easy, you can use dynamic cursors...
create or replace PROCEDURE Get_Type_One_Polygon_Values
(in_role VARCHAR2, rc_generic OUT SYS_REFCURSOR) as
sql varchar2(100);
BEGIN
sql :='SELECT ' || in_role || '_POLY_ID, '
|| in_role || '_POLY_NAME '
|| 'FROM TEAMCHK.' || in_role;
open rc_generic for sql;
END;
It may be beneficial to use column aliases POLY_ID and POLY_NAME to unify them all in the refcursor.

Related

Simple Stored procedure in Snowflake Scripting

I need to create a table in snowflake stored procedure using sql. Below is the code
create or replace procedure sp(tmp_table varchar,col_name varchar,d_type varchar )
returns varchar not null
as
$$
BEGIN
drop table if exists identifier(:tmp_table);
create table identifier(:tmp_table) (identifier(:col_name) identifier(:d_type));
END;
$$;
I am getting the error as
syntax error line 4 at position 24 unexpected '('. (line 4)
Could you please help me on this issue?
Bind variables are not supported in columns, this is why your script fails. You can use EXECUTE IMMEDIATE to generate a dynamic SQL to overcome this issue:
create or replace procedure sp(tmp_table varchar,col_name varchar,d_type varchar )
returns varchar not null
as
$$
BEGIN
drop table if exists identifier(:tmp_table);
execute immediate 'create table ' || :tmp_table || '(' || :col_name || ' ' || :d_type || ')' ;
END;
$$;
Unfortunately, it isn't possible to dynamically name columns in this way using Snowflake Scripting [1]. As an alternative you can dynamically generate your SQL statements as text to then execute.
I've swapped the drop table for create or replace as it does the same function, but in one command.
create or replace procedure sp(tmp_table varchar, col_name varchar, d_type varchar)
returns table (result varchar)
language sql
as
DECLARE
sql_text varchar;
rs resultset;
invalid_input EXCEPTION (-20001, 'Input contains whitespace.');
BEGIN
IF ((SELECT :tmp_table regexp '(^\\S*$)')=FALSE) THEN
RAISE invalid_input;
END IF;
IF ((SELECT :col_name regexp '(^\\S*$)')=FALSE) THEN
RAISE invalid_input;
END IF;
IF ((SELECT :d_type regexp '(^\\S*$)')=FALSE) THEN
RAISE invalid_input;
END IF;
sql_text := 'create or replace table ' || :tmp_table || '(' || :col_name || ' ' || :d_type || ')' ;
rs := (execute immediate :sql_text);
return table(rs);
END;
Note: In the example above I've included some code to check for whitespace in the inputs to minimise the potential for SQL injection. This is important to stop users abusing the procedure. Additional checks would be prudent. You should also ensure that this Stored Procedure runs as the least privilege role possible to further minimise the scope for abuse.
Example as JavaScript (without SQL injection protection):
CREATE OR REPLACE procedure sp(TMP_TABLE varchar, COL_NAME varchar, D_TYPE varchar)
RETURNS varchar not null
LANGUAGE javascript
AS
$$
var sql_cmd = "DROP TABLE IF EXISTS " + TMP_TABLE + ";";
var stmt = snowflake.createStatement(
{sqlText: sql_cmd}
);
var res = stmt.execute();
sql_cmd = "CREATE TABLE " + TMP_TABLE + " (" + COL_NAME + " " + D_TYPE + ");";
stmt = snowflake.createStatement(
{sqlText: sql_cmd}
);
res = stmt.execute();
res.next();
return res.getColumnValue(1);
$$
;

Calling a procedure inside a procedure with independent sessions

I am trying to call a procedure inside a procedure but this gives me an error like:
Uncaught exception of type 'STATEMENT_ERROR' on line 19 at position 2 : This session warehouse WH_STD_EDWQA_ANALYST no longer exists.
My parent procedure construct is like creating a warehouse & the child procedure is to populate a metadata table(custom) by use of table(result_scan(last_query_id())).
Parent procedure construct:
create or replace procedure wh_resource_govern(type varchar, env varchar, ..., varchar)
returns varchar not null
language sql
as
$$
declare
wh_name varchar;
wh_setup varchar;
lv_acct_name varchar;
begin
wh_name := 'WH_' || type || '_' || env || '_' || team;
wh_setup := 'CREATE OR REPLACE WAREHOUSE' || ' ' || wh_name || ' ' || 'WITH' || ' '
|| 'WAREHOUSE_SIZE = ' || v_wh_size || ' '
...,
|| 'COMMENT= '|| '"' || v_created_by || '"' ;
execute immediate wh_setup;
commit;
call load_all_warehouse_metadata('a', 'b', 'c'); ----> This is where it is getting stuck.
end;
$$
;
Child procedure construct is given as below:
create or replace procedure load_all_warehouse_metadata(wh_type varchar, wh_env varchar, wh_team varchar)
returns varchar not null
language sql
as
$$
declare
lv_acct_name varchar;
begin
select current_account() into lv_acct_name;
show warehouses;
insert into ALL_WAREHOUSE_METADATA (account_name, warehouse_type, .., .., )
select :lv_acct_name, :wh_type, :wh_env, :wh_team, "name", ..., ...,
from table(result_scan(last_query_id()));
end;
$$
;
Any inputs on how to address this would be really helpful.
Creating a warehouse immediately makes it the current warehouse for the session, example:
create or replace warehouse FOO;
select current_warehouse(); -- FOO
drop warehouse FOO();
select current_warehouse(); -- NULL
When you run execute immediate wh_setup; in the first SP, it's setting the session's warehouse to the one you just created. Calling a child SP using owner's rights (default) from a warehouse that isn't the one that started the SP is causing context problems for the warehouse.
You can reproduce this error as follows:
create or replace procedure SP1()
returns varchar not null
language sql
--execute as caller
as
$$
declare
currentWarehouse varchar;
begin
select current_warehouse() into currentWarehouse;
create or replace warehouse FOO;
call SP2();
return currentWarehouse;
end;
$$;
create or replace procedure SP2()
returns varchar not null
language sql
--execute as caller
as
$$
declare
currentWarehouse varchar;
begin
select current_warehouse() into currentWarehouse;
create or replace temp table FOO(s string);
insert into FOO(S) values ('Bar');
return 'Done';
end;
$$;
call SP1();
You can fix this code sample immediately by uncommenting the two commented-out ownership options for SP1 and SP2:
--execute as caller (Remove the comment markers and recreate both SPs.)
You can also run a SQL command to use warehouse <wh_name> in your SP(s), but you must run the SP as caller in order to change warehouse context this way.

Range partitioning tables with data in DB2 LUW

First of all my question will be, if there is a way of auto-generation partitions (like interval in Oracle for range partitioning) while inserting data into the partitioned table in DB2?
At the moment I have a schema with some hundreds of tables, which are not partitioned. And I suppose to partition them.
My steps will be:
rename all the tables to OLD_table_name
execute DDLs for those tables but already partitioned (by load_id column int data type)
execute for all, insert into table_name select * from OLD_table_name
...and here it starts.
(Of course the process must be automatically and I don't know which values contain load_id column + they will be all different for all the tables, otherwise it would be possible to generate simply alter statements and execute them).
Therefore I would go for cursor.
For the moment I have solution which is working but I don't like it:
BEGIN
FOR CL AS MYCURS INSENSITIVE CURSOR FOR
select distinct 'alter table '||tb_nm||'
add partition PART_'||lpad(load_id,5,0)||'
starting from '||load_id||'
ending at '||load_id v_alt
from (
select load_id,'Table_01' tb_nm from Table_01
union
select load_id ,'Table_02'from Table_02
union
....
/*I have generated this union statements for whole set of tables*/)
do
execute immediate v_alt;
end for;
end
Also, I have tried some more elegant (on my opinion) variant, but didn't sucseed:
BEGIN
DECLARE v_stmnt VARCHAR(1000);
DECLARE v_check_val int;
DECLARE v_prep_stmnt STATEMENT;
for i as (select table_name
from sysibm.tables
where TABLE_SCHEMA ='shema_name'
)
do
SET v_stmnt = 'set ? = (SELECT distinct(load_id) FROM '||table_name||')';
PREPARE v_prep_stmnt FROM v_stmnt;
/*and here I stuck. I assume there must be possibility to run next execute as well in loop, but
all my attempts were not succsesfull*/
--EXECUTE v_prep_stmnt into v_check_val ;
end for;
END
Would highly appreciate any hint.
Try something like this:
--#SET TERMINATOR #
SET SERVEROUTPUT ON#
BEGIN
DECLARE L_TABSCHEMA VARCHAR(128) DEFAULT 'SCHEMA_NAME';
DECLARE L_COLNAME VARCHAR(128) DEFAULT 'LOAD_ID';
DECLARE L_VALUE INT;
DECLARE L_STMT VARCHAR(1024);
DECLARE SQLSTATE CHAR(5);
DECLARE C1 CURSOR FOR S1;
FOR I AS
SELECT TABNAME
FROM SYSCAT.COLUMNS
WHERE TABSCHEMA = L_TABSCHEMA AND COLNAME = L_COLNAME
DO
PREPARE S1 FROM 'SELECT DISTINCT(' || L_COLNAME || ') FROM ' || L_TABSCHEMA || '."' || I.TABNAME ||'"';
OPEN C1;
L1: LOOP
FETCH C1 INTO L_VALUE;
IF SQLSTATE<>'00000' THEN LEAVE L1; END IF;
SET L_STMT =
'alter table ' || L_TABSCHEMA || '."' || I.TABNAME || '" '
||'add partition PART_' || lpad(L_VALUE, 5, 0) || ' starting from ' || L_VALUE || ' ending at ' || L_VALUE;
--CALL DBMS_OUTPUT.PUT_LINE(L_STMT);
EXECUTE IMMEDIATE L_STMT;
END LOOP;
CLOSE C1;
END FOR;
END
#

can one use temp tables in teradata

Is there an equivalent for this sql server tsql in teradata:
IF OBJECT_ID('tempdb..#SomeTempTable') IS NOT NULL DROP TABLE #SomeTempTable;
CREATE TABLE #SomeTempTable (Bla NVARCHAR(255));
INSERT INTO #SomeTempTable
SELECT N'a'
UNION ALL
SELECT N'B'
There's no equivalent for the 1st statement, there might be a Stored Procedure for it like this:
REPLACE PROCEDURE Drop_Table_If_Exists
(
IN db_name VARCHAR(128) CHARACTER SET UNICODE,
IN tbl_name VARCHAR(128) CHARACTER SET UNICODE,
OUT msg VARCHAR(400) CHARACTER SET UNICODE
) SQL SECURITY INVOKER
BEGIN
DECLARE full_name VARCHAR(361) CHARACTER SET UNICODE;
DECLARE sql_stmt VARCHAR(500) CHARACTER SET UNICODE;
DECLARE exit HANDLER FOR SQLCODE 'T3807'--SQLEXCEPTION
BEGIN
IF SQLCODE = 3807 THEN SET msg = full_name || ' doesn''t exist.';
ELSE
RESIGNAL;
END if;
END;
SET full_name = '"' || COALESCE(db_name,DATABASE) || '"."' || tbl_name || '"';
SET sql_stmt = 'DROP TABLE ' || full_name || ';';
EXECUTE IMMEDIATE sql_stmt;
SET msg = full_name || ' dropped.';
END;
A VOLATILE table exists only within your current session (i.e. the same name might be used in different sessions for different tables) and is automatically dropped when the session disconnects. When you keep the naming convention (name of a temporary starts with #) you probably don't need the conditional Drop (you should know if you already created this table in the current session):
CREATE VOLATILE TABLE #SomeTempTable(
Bla VARCHAR(255) CHARACTER SET UNICODE)
ON COMMIT PRESERVE ROWS;
Caution, if you don't specify the Primary Index it will default to a NUPI on the first column.
The Select has a strange restriction, you need a FROM when you do a Set Operation like UNION/INTERSECT/EXCEPT. Workaround is either a dummy view (similar to Oracle's DUAL table) like this:
replace view dummy as select 1 as x;
INSERT INTO #SomeTempTable
SELECT 'a' FROM dummy
UNION ALL
SELECT 'B' FROM dummy
or a similar CTE:
INSERT INTO #SomeTempTable
WITH dummy AS (select 1 as x)
SELECT 'a' FROM dummy
UNION ALL
SELECT 'B' FROM dummy

Restart initial id of table with using MAX() method

I am doing some changes on my table and I couldn't figure out the problem.
This is my SQL script;
ALTER TABLE X ALTER COLUMN X_ID RESTART WITH (SELECT MAX(X_ID) FROM X);
Altough I used AS instead of WITH and tried other combinations, I couldn't find the exact syntax. (By the way, I cannot set this property in the initialization, I got to do it after creation of the table )
When looking at the syntax for ALTER TABLE you will find that you can only use a constant, e.g., "RESTART WITH 12345". A query itself is not possible. For automation you would need to break it up, use a variable, generate the ALTER statement and execute it.
Assuming this is for DB2 for LUW, you can automate the process of resetting identity values with some dynamic SQL:
begin
declare curmax bigint;
for r as select tabschema, tabname, colname, nextcachefirstvalue, seqid
from syscat.colidentattributes where tabschema = current_schema
do
prepare s from
'select max(' || r.colname || ') from ' ||
rtrim(r.tabschema) || '.' || r.tabname;
begin
declare c cursor for s;
open c;
fetch c into curmax;
close c;
end;
if curmax >= r.nextcachefirstvalue
then
execute immediate
'alter table ' || rtrim(r.tabschema) || '.' || r.tabname ||
' alter column ' || r.colname || ' restart with ' || varchar(curmax+1);
end if;
end for;
end
You may need to change the data type of curmax if your identities are not integer, and adjust the query against syscat.colidentattributes to use the appropriate schema name.

Resources