How to create a Procedure with a specific WITH clause in Oracle? - sql

I have a problem by creating a Procdure with a specific WITH clause. I use an Oracle database. I need a temp table for mapping specific values during time of execution.
In addition I have the following Oracle procedure. This Procedure will be created and executed by bringing into database:
How can I create this kind of Oracle SQL Procedure? Many thanks for helping me!

Use your WITH clause in a cursor FOR loop in your procedure:
DECLARE
PROCEDURE myProcedure(pinSchema IN VARCHAR2) AS
BEGIN
FOR aRow IN (WITH t AS (SELECT 'myFirstOldSchema' AS OLD_SCHEMA,
'myFirstNewSchema' AS NEW_SCHEMA
FROM DUAL UNION ALL
SELECT 'mySecondOldSchema' AS OLD_SCHEMA,
'mySecondNewSchema' AS NEW_SCHEMA
FROM DUAL UNION ALL
SELECT 'myThirdOldSchema' AS OLD_SCHEMA,
'myThirdNewSchema' AS NEW_SCHEMA
FROM DUAL)
SELECT t.*
FROM t
WHERE t.OLD_SCHEMA = pinSchema)
LOOP
-- Here a specific value from column "OLD_SCHEMA" must be inserted
EXECUTE IMMEDIATE 'INSERT INTO ' || aRow.OLD_SCHEMA ||
'.myTable(column_1, column_2, column_3)
(SELECT extern_column_1,
extern_column_2,
extern_column_3
FROM ' || aRow.NEW_SCHEMA || '.myExternTable)';
END LOOP;
END myProcedure;
BEGIN
FOR S IN (SELECT * FROM ROOT_SCHEMA.myTableWithSchema)
LOOP
-- First loop S.mySchemata represent the value 'myFirstOldSchema'
-- Second loop S.mySchemata represent the value 'mySecondOldSchema'
-- Third loop S.mySchemata represent the value 'myThirdOldSchema'
myProcedure(S.mySchemata);
END LOOP
COMMIT;
END;
Not tested on animals - you'll be first!

Related

Create UNION ALL statements via a loop

Manually, I can select partitions in an inner query with the first code block below. Is there a way to do this in a more elegant way via a loop? I'm showing 3 partitions here, but I have about 200 and the partitions are based on a date column and therefore the partition names will need to change when I run this query again at a future date.
SELECT *
FROM (
SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION("SYS_P211048") UNION ALL
SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION("SYS_P210329") UNION ALL
SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION("SYS_P176323")
) TRX_ARQ
;
With this statement, I've created a loop that outputs the UNION ALL statements.
BEGIN
FOR ALL_TAB_PARTITIONS IN
(
SELECT PARTITION_NAME
FROM ALL_TAB_PARTITIONS
where TABLE_OWNER = 'TABLEOWNER'
AND TABLE_NAME = 'TABLENAME'
AND PARTITION_POSITION > 123
ORDER BY partition_position DESC
)
LOOP
DBMS_OUTPUT.PUT_LINE( 'SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION(\"'
|| ALL_TAB_PARTITIONS.PARTITION_NAME || '\") UNION ALL');
END LOOP;
END;
And in this block, I've attempted to use the loop inside the inner query. It's not yet formatted correctly and I'll need to avoid having UNION ALL for the very last partition.
SELECT *
FROM (
BEGIN
FOR ALL_TAB_PARTITIONS IN
(
SELECT PARTITION_NAME
FROM ALL_TAB_PARTITIONS
where TABLE_OWNER = 'TABLEOWNER'
AND TABLE_NAME = 'TABLENAME'
AND PARTITION_POSITION > 123
ORDER BY partition_position DESC
)
LOOP
DBMS_OUTPUT.PUT_LINE( 'SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION(\"'
|| ALL_TAB_PARTITIONS.PARTITION_NAME || '\") UNION ALL');
END LOOP;
END;
) TRX_ARQ
;
Here are some of the errors, but there were also many more. They are syntax errors pointing to other parts of the query so I would expect that I have an issue with escaping the quotes.
Error starting at line : 99 in command -
END LOOP
Error report -
Unknown Command
Error starting at line : 100 in command -
END
Error report -
Unknown Command
Error starting at line : 101 in command -
)
Error report -
Unknown Command
Error starting at line : 102 in command -
) TABLENAME
Error report -
Unknown Command
This is a bit of a guess, but it's too long for a comment.
I am assuming your table is interval partitioned. In that case, getting all the data from partition positions > 123 is the same as getting all the rows with a higher date than the highest date in partition 123.
You can obtain that date from ALL_TAB_PARTITIONS and then use it to query the table. Like this:
WITH FUNCTION get_high_value RETURN DATE IS
l_high_val_expr ALL_TAB_PARTITIONS.HIGH_VALUE%TYPE;
l_high_value DATE;
BEGIN
SELECT high_value
INTO l_high_val_expr
FROM all_tab_partitions
WHERE table_owner = 'RSS_ACQ'
AND table_Name = 'TRX_ARQ'
and partition_position = 123;
EXECUTE IMMEDIATE 'SELECT ' || l_high_val_expr || ' FROM DUAL' INTO l_high_value;
RETURN l_high_value;
END;
SELECT * FROM rss_acq.trx_arq
-- Replace "partitioned_date_column" with the name of the column on which the
-- table is interval partitioned.
WHERE partitioned_date_column > get_high_value;
We can't execute an anonymous PL/SQL block in a SELECT statement.
What you need to do is spool the output of the ALL_TAB_PARTITIONS loop to a file (or a SQL worksheet if you're using an IDE like SQL Developer). This will give you a script you can run separately after editing it (you need to trim UNION ALL from the final generated SELECT.
Probably there are more elegant ways of achieving the same thing, but the task seems sufficiently wrong that it doesn't strike me as being worth the effort. You want to query 200 partitions in a single statement. That is a brute force operation and there isn't mush to be gained from querying named blocks. In fact, producing a union of 200 separate queries may be more expensive than a single query. So why not try something like this?
select * from RSS_ACQ.TRX_ARQ
where partition_key_col >= date '2018-08-01' -- or whatever
"I think you are overlooking the 12c feature of using PL/SQL in the WITH clause"
That 12c feature is for functions not procedures, so it won't help the OP run their code. It would be possible to use a WITH clause function but that would require:
creating a type with the same projection as the target table
and a nested table type based on that type
a WITH clause function which assembles and executes a dynamic SQL statement
we can't use REF CURSORs in SQL so ...
the function has to execute the dynamic select INTO a local collection variable ...
then loop over the collection and PIPE ROW to output those rows ...
so the main query can call the function with a table() call
Can a WITH clause function be pipelined? I can't find anything in the documentation to say we can't (don't have access to 12c right now to test).

How to store multiple rows in a variable in pl/sql function?

I'm writing a pl/sql function. I need to select multiple rows from select statement:
SELECT pel.ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
if i use:
SELECT pel.ceid
INTO v_ceid
it only stores one value, but i need to store all values that this select returns. Given that this is a function i can't just use simple select because i get error, "INTO - is expected."
You can use a record type to do that. The below example should work for you
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
BEGIN
SELECT x
BULK COLLECT INTO
var
FROM (
SELECT 1 x
FROM dual
UNION
SELECT 2 x
FROM dual
UNION
SELECT 3 x
FROM dual
);
FOR I IN 1..3 LOOP
dbms_output.put_line(var(I));
END LOOP;
END;
So in your case, it would be something like
select pel.ceid
BULK COLLECT INTO <variable which you create>
from pa_exception_list
where trunc(pel.creation_Date) >= trunc(sysdate-7);
If you really need to store multiple rows, check BULK COLLECT INTO statement and examples. But maybe FOR cursor LOOP and row-by-row processing would be better decision.
You may store all in a rowtype parameter and show whichever column you want to show( assuming ceid is your primary key column, col1 & 2 are some other columns of your table ) :
SQL> set serveroutput on;
SQL> declare
l_exp pa_exception_list%rowtype;
begin
for c in ( select *
from pa_exception_list pel
where trunc(pel.creation_date) >= trunc(SYSDATE-7)
) -- to select multiple rows
loop
select *
into l_exp
from pa_exception_list
where ceid = c.ceid; -- to render only one row( ceid is primary key )
dbms_output.put_line(l_exp.ceid||' - '||l_exp.col1||' - '||l_exp.col2); -- to show the results
end loop;
end;
/
SET SERVEROUTPUT ON
BEGIN
FOR rec IN (
--an implicit cursor is created here
SELECT pel.ceid AS ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
)
LOOP
dbms_output.put_line(rec.ceid);
END LOOP;
END;
/
Notes from here:
In this case, the cursor FOR LOOP declares, opens, fetches from, and
closes an implicit cursor. However, the implicit cursor is internal;
therefore, you cannot reference it.
Note that Oracle Database automatically optimizes a cursor FOR LOOP to
work similarly to a BULK COLLECT query. Although your code looks as if
it fetched one row at a time, Oracle Database fetches multiple rows at
a time and allows you to process each row individually.

looping and executing dynamic query in Oracle

I'm having some trouble truncating some tables that get generated in my base.
The table names get saved and I can do it manually, but I wanted to see if it could be automated until its fixed.
What I've done so far is get all the table names and a id/number into my own help table. My errors begin around the loop/Execute immediate where im not sure how to use the data I've gotten in the syntax and i cant find any similar examples.
create table HlpTruncTable as SELECT SUBSTR(argument, 3) as tblName, rownum as Nr
FROM tblLogHlp
WHERE status = 'E' and argument like '0,awfh%' and LAST_UPDATE <= ADD_MONTHS(sysdate,-1);
for i in 1..(select max(nr) from HlpTruncTable) LOOP
execute immediate TRUNCATE TABLE (select tblName from HlpTruncTable where nr = (i));
END LOOP;
drop table hlpTruncTable;
I would do it like this:
declare
cursor HlpTruncTable is
SELECT SUBSTR(argument, 3) as tblName
FROM tblLogHlp
WHERE status = 'E' and argument like '0,awfh%' and LAST_UPDATE <= ADD_MONTHS(sysdate,-1);
BEGIN
FOR aTable IN HlpTruncTable LOOP
execute immediate 'TRUNCATE TABLE '||aTable.tblName;
END LOOP;
END;

nzsql - Converting a subquery into columns for another select

Goal: Use a given subquery's results (a single column with many rows of names) to act as the outer select's selection field.
Currently, my subquery is the following:
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'test_table' AND column_name not in ('colRemove');
What I am doing in this subquery is grabbing all the column names from a table (i.e. test_table) and outputting all except for the column name specified (i.e. colRemove). As stated in the "goal", I want to use this subquery as such:
SELECT (*enter subquery from above here*)
FROM actual_table
WHERE (*enter specific conditions*)
I am working on a Netezza SQL server that is version 7.0.4.4. Ideally, I would like to make the entire query executable in one line, but for now, a working solution would be much appreciated. Thanks!
Note: I do not believe that the SQL extensions has been installed (i.e. arrays), but I will need to double check this.
A year too late, here's the best I can come up with but, as you already noticed, it requires a stored procedure to do the dynamic SQL. The stored proc creates a view with the all the columns from the source table minus the one you want to exclude.
-- Create test data.
CREATE TABLE test (firstcol INTEGER, secondcol INTEGER, thirdcol INTEGER);
INSERT INTO test (firstcol, secondcol, thirdcol) VALUES (1, 2, 3);
INSERT INTO test (firstcol, secondcol, thirdcol) VALUES (4, 5, 6);
-- Install stored procedure.
CREATE OR REPLACE PROCEDURE CreateLimitedView (varchar(ANY), varchar(ANY)) RETURNS BOOLEAN
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
tableName ALIAS FOR $1;
columnToExclude ALIAS FOR $2;
colRec RECORD;
cols VARCHAR(2000); -- Adjust as needed.
isfirstcol BOOLEAN;
BEGIN
isfirstcol := true;
FOR colRec IN EXECUTE
'SELECT ATTNAME AS NAME FROM _V_RELATION_COLUMN
WHERE
NAME=UPPER('||quote_literal(tableName)||')
AND ATTNAME <> UPPER('||quote_literal(columnToExclude)||')
ORDER BY ATTNUM'
LOOP
IF isfirstcol THEN
cols := colRec.NAME;
ELSE
cols := cols || ', ' || colRec.NAME;
END IF;
isfirstcol := false;
END LOOP;
-- Should really check if 'LimitedView' already exists as a view, table or synonym.
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW LimitedView AS SELECT ' || cols || ' FROM ' || quote_ident(tableName);
RETURN true;
END;
END_PROC
;
-- Run the stored proc to create the view.
CALL CreateLimitedView('test', 'secondcol');
-- Select results from the view.
SELECT * FROM limitedView WHERE firstcol = 4;
FIRSTCOL | THIRDCOL
----------+----------
4 | 6
You could have the stored proc return a resultset directly but then you wouldn't be able to filter results with a WHERE clause.

How to choose tables on select from all_tables?

I have the following table name template, there are a couple with the same name and a number at the end: fmj.backup_semaforo_geo_THENUMBER, for example:
select * from fmj.backup_semaforo_geo_06391442
select * from fmj.backup_semaforo_geo_06398164
...
Lets say I need to select a column from every table which succeeds with the 'fmj.backup_semaforo_geo_%' filter, I tried this:
SELECT calle --This column is from the backup_semaforo_geo_# tables
FROM (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%');
But I'm getting the all_tables tables name data:
TABLE_NAME
----------
BACKUP_SEMAFORO_GEO_06391442
BACKUP_SEMAFORO_GEO_06398164
...
How can I achieve that without getting the all_tables output?
Thanks.
Presumably your current query is getting ORA-00904: "CALLE": invalid identifier, because the subquery doesn't have a column called CALLE. You can't provide a table name to a query at runtime like that, unfortunately, and have to resort to dynamic SQL.
Something like this will loop through all the tables and for each one will get all the values of CALLE from each one, which you can then loop through. I've used DBMS_OUTPUT to display them, assuming you're doing this in SQL*Plus or something that can deal with that; but you may want to do something else with them.
set serveroutput on
declare
-- declare a local collection type we can use for bulk collect; use any table
-- that has the column, or if there isn't a stable one use the actual data
-- type, varchar2(30) or whatever is appropriate
type t_values is table of table.calle%type;
-- declare an instance of that type
l_values t_values;
-- declare a cursor to generate the dynamic SQL; where this is done is a
-- matter of taste (can use 'open x for select ...', then fetch, etc.)
-- If you run the query on its own you'll see the individual selects from
-- all the tables
cursor c1 is
select table_name,
'select calle from ' || owner ||'.'|| table_name as query
from all_tables
where owner = 'FMJ'
and table_name like 'BACKUP_SEMAFORO_GEO%'
order by table_name;
begin
-- loop around all the dynamic queries from the cursor
for r1 in c1 loop
-- for each one, execute it as dynamic SQL, with a bulk collect into
-- the collection type created above
execute immediate r1.query bulk collect into l_values;
-- loop around all the elements in the collection, and print each one
for i in 1..l_values.count loop
dbms_output.put_line(r1.table_name ||': ' || l_values(i));
end loop;
end loop;
end;
/
May be a dynamic SQL in a PLSQL program;
for a in (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%')
LOOP
sql_stmt := ' SELECT calle FROM' || a.table_name;
EXECUTE IMMEDIATE sql_stmt;
...
...
END LOOP;