I have many procedures that do the same thing:
they refresh materialized view and check if the count is not 0, then push that data into production tables. this is the skeleton of what each one does, the only thing that changes is the name of the materialized view. I thought about creating one function that will take in the name of the MV and process it, but it is not working :(
create or replace
function REFRESH_MV (mv_to_refresh IN VARCHAR2)
RETURN VARCHAR2
AUTHID CURRENT_USER
AS
COUNTS INT;
begin
DBMS_MVIEW.REFRESH(mv_to_refresh,'C');
COMMIT;
SELECT COUNT(*) INTO COUNTS FROM 'SEMANTIC.' || mv_to_refresh;
IF COUNTS = 0 THEN
RETURN 'SEMANTIC.' || mv_to_refresh || ' is empty';
ELSE
'SEMANTIC_READ_ONLY.' || RELOAD_TABLE(mv_to_refresh);
RETURN 'SEMANTIC_READ_ONLY.' || mv_to_refresh || ' has been refreshed today';
END IF;
EXCEPTION WHEN OTHERS THEN NULL;
end;
You have to use EXECUTE IMMEDIATE or DBMS_SQL to do that; the first one should be easier to use in your case.
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM 'SEMANTIC.' || mv_to_refresh INTO COUNTS;
should do the trick.
You should use dynamic SQL for this purpose:
CREATE OR REPLACE FUNCTION REFRESH_MV (mv_to_refresh IN VARCHAR2)
RETURN VARCHAR2
AUTHID CURRENT_USER
AS
COUNTS INT;
VSQL VARCHAR2(100);
begin
DBMS_MVIEW.REFRESH('SEMANTIC.' || mv_to_refresh, 'C');
COMMIT;
VSQL := 'SELECT COUNT(1) FROM SEMANTIC.' || mv_to_refresh;
EXECUTE IMMEDIATE VSQL INTO COUNTS;
IF COUNTS = 0 THEN
RETURN 'SEMANTIC.' || mv_to_refresh || ' is empty';
ELSE
SEMANTIC_READ_ONLY.RELOAD_TABLE(mv_to_refresh);
RETURN 'SEMANTIC_READ_ONLY.' || mv_to_refresh
|| ' has been refreshed today';
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN 'Error has occured: ' || SQLERRM;
END;
Please make sure you pass view name without schema prefix as input parameter.
You should also note that scince it function it should return value or raise exception. But in you example function will return nothing in case of exception.
I didn't quite get the semantics of RELOAD_TABLE() procedure. In example given it is supposed to be a some procedure in SEMANTIC_READ_ONLY schema. In case you really need the appropriate function to be evaluated dynamically, you again can use dynamic SQL to construct the valid string contaning the code and call it:
vsql := 'begin SCHEMA_NAME.' || GET_PROCEDURE_FOR(mv_to_refresh) || '; end;';
execute immediate vsql;
Related
let's see if somebody can help me, I need to delete rows from different tables and I did think to do it using an array so i wrote this :
DECLARE
TYPE mytype_a IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
mytype mytype_a;
BEGIN
mytype(mytype.count + 1) := 'MYTABLE';
FOR i IN 1 .. mytype.count
LOOP
DELETE mytype(i) WHERE valid = 'N';
END LOOP;
END;
Trying to run this piece of code using sqldeveloper I get the ORA-00933 command not properly ended, if I put directly the table name it works, what am I doing wrong?
Thank you.
Thank you very much guys, it works perfectly.
This is not the correct approach. You have to use Dynamic SQL for this -
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar(500) := NULL;
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid =''N''';
EXECUTE IMMEDIATE stmt;
end loop;
END;
You would need to use dynamic SQL, concatenating the table name from the collection into the statement, inside your loop:
execute immediate 'DELETE FROM ' || mytype(i) || ' where valid = ''N''';
Or you can put the statement into a variable so you can display it for debugging purposes, and then execute that, optionally with a bind variable for the valid value:
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
... which I've made also display how many rows were deleted from each table. Note though that you shoudln't rely on the caller being able to see anything printed with dbms_output - it's up to the client whether it shows it.
The whole anonymous block would then be:
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar2(4000);
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
end loop;
END;
/
You could use a built-in collection type to simplify it even further.
db<>fiddle showing some options.
Hopefully this doesn't apply, but if you might have any tables with quoted identifiers then you would need to add quotes in the dynamic statement, e.g.:
stmt := 'DELETE FROM "' || mytype(i) || '" where valid = :valid';
... and make sure the table name values in your collection exactly match the names as they appear in the data dictionary (user_tables.table_name).
I'm creating a function that accepts two parameters. And one of my parameter will serve as the database link for my statement. I've tried concatenating it. How will I be able to achieve this?
It shows this error
ORA-00923: FROM keyword not found where expected ORA-06512 at
"NOINK.CHECK_SECOND_REF_DIE", line 13.
Below is the code.
drop function check_second_ref_die;
create or replace function check_second_ref_die(lotNumber in VARCHAR2, db_link in VARCHAR2)
return varchar2
is
row_count NUMBER;
sql_statement VARCHAR2(300);
BEGIN
sql_statement := 'SELECT COUNT(*) FROM wcrepo.WCR_WAFER_REFERENCE#lepftds.itg.ti.com
WHERE waferconfigfile = (SELECT waferconfigfile FROM program_setup_rev#' || db_link ||
'WHERE device = (SELECT device FROM noink.lot WHERE lot_num = ' ||lotNumber || ')
AND setup_cnt=0) AND status =' || 'Approved' || 'AND ref_die_type =' || 'Secondary';
execute immediate sql_statement into row_count;
IF (row_count != 0) THEN
RETURN 'TRUE';
ELSE
RETURN'FALSE';
END IF;
END;
This is the code when I try to call the function
SELECT CASE
WHEN check_second_ref_die ('8019572', 'rfabtwdb.dal.make.ti.com') = 'TRUE'
THEN 'EXISTS' ELSE 'NOT EXISTS'
END
AS RESULT
FROM DUAL
AND status =' || 'Approved' || 'AND
This is wrong. Remove the concatenation operators and we have ...
AND status =ApprovedAND
... which is not valid SQL. To reference string literals you need to escape single quotes. The simplest way is to use two of them:
AND status =''Approved'' AND
You'll need to fix all the string literals in your code.
Dynamic SQL is hard because it turns compilation errors into runtime errors. You can make it easier to debug your code by including some simple instrumentation. If your code had this line before the EXECUTE IMMEDIATE you could have seen the executed statement and probably spotted the bloomer for yourself.
dbms_output.put_line(v_sql);
CREATE OR REPLACE PROCEDURE country_demographics
(p_country_name IN countries.country_name%TYPE,
p_country_demo_rec OUT ed_type)
IS
TYPE ed_type IS RECORD (
c_name countries.country_name%TYPE,
c_location countries.location%TYPE,
c_capitol countries.capitol%TYPE,
c_population countries.population%TYPE,
c_airports countries.airports%TYPE,
c_climate countries.climate%TYPE);
BEGIN
SELECT country_name, location, capitol, population, airports, climate
INTO ed_type.c_name, ed_type.c_location, ed_type.c_capitol, ed_type.population, ed_type.airports, ed_type.climate
FROM countries;
DBMS_OUTPUT.PUT_LINE('Country Name:' || v_country_demo_rec.country_name ||
'Location:' || v_country_demo_rec.location ||
'Capitol:' || v_country_demo_rec.capitol ||
'Population:' || v_country_demo_rec.population ||
'Airports:' || v_country_demo_rec.airports ||
'Climate:' || v_country_demo_rec.climate );
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20201, 'This country does not exist.');
END IF;
END;
The problem is asking me to create a procedure called country_demograhics. Pass the country_name as an IN parameter. Display CONTRY_NAME, LOCATION, CAPITOL, POPULATION, AIRPORTS, CLIMATE. Use a user-defined record structure for the INTO clause of your select statement. Raise an exception if the country does not exist.
Now here is a copy of my code, that keeps coming back with an error of:
Error at line 0: PL/SQL: Compilation unit analysis terminated.
That error should be the second error which tells you, it will not look any further. There should be another error too. I guess that ed_type doesn't exist outside of the procedure so it can not have an ed_type as OUT parameter. ed_type isn't known outside.
First thing - Look you used the different variable in declaring(p_country_demo_rec ) and begin(v_country_demo_rec) part. I think that might be one mistake.
Try following script:- it may help you.
CREATE OR REPLACE PROCEDURE COUNTRY_DEMOGRAPHICS
IS
TYPE ED_TYPE IS TABLE OF countries%ROWTYPE;
p_country_demo_rec ED_TYPE;
BEGIN
SELECT * BULK COLLECT INTO p_country_demo_rec FROM countries;
FOR i IN p_country_demo_rec.FIRST..p_country_demo_rec.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('Country Name:'||p_country_demo_rec(i).country_name ||
'Location:' || p_country_demo_rec(i).location ||
'Capitol:' || p_country_demo_rec(i).capitol ||
'Population:' || p_country_demo_rec(i).population ||
'Airports:' || p_country_demo_rec(i).airports ||
'Climate:' || p_country_demo_rec(i).climate );
END LOOP;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20201, 'This country does not exist.');
END IF;
END;
/
EXECUTE COUNTRY_DEMOGRAPHICS;
Note:- You can use the one parameter(IN parameter) in a procedure to get the specific country demographics data and that parameter use in select statement for filter out the specific country.
Example:
CREATE OR REPLACE PROCEDURE COUNTRY_DEMOGRAPHICS(p_country_name IN varchar2)
Select statement looks like:
SELECT * BULK COLLECT INTO p_country_demo_rec FROM countries where
country_name = ||p_country_name;
Execute part:
EXECUTE COUNTRY_DEMOGRAPHICS(p_country_name);
The problem is, how to transfer CLOB data from one source database to another Oracle database, having DBLinks.
Oracle cannot transfer CLOB data using DBLinks so what kind of solution we can have apart from: extending fields in Oracle to Varchar2 32.767 characters (new feature of Oracle 12).
At first you need temporary table:
create global temporary table TBL_TMP_CLOB
(
c_clob CLOB
)
At second use 'Insert from Select':
INSERT INTO schema.remote_table#dblink(rem_clob) SELECT * FROM TBL_TMP_CLOB;
I released a Github project that queries CLOBs and BLOBs over a dblink.
https://github.com/HowdPrescott/Lob_Over_DBLink
Here's the CLOB part in a stand alone function:
create or replace function dblink_clob(
p_dblink in varchar2
, p_clob_col in varchar2
, p_rid in urowid
)
return clob is
/** A function to fetch a CLOB column over a dblink **/
/** Laurence Prescott 25-Aug-17 **/
/** select dblink_clob('some_dblink', 'some_clob_column', rowid)
from some_table#some_dblink;
Note: Does not work on tables that have a virtual column (eg. xmltype).
**/
c_chunk_size constant pls_integer := 4000;
v_chunk varchar2(4000);
v_remote_table varchar2(128);
v_clob clob;
v_pos pls_integer := 1;
begin
dbms_lob.createtemporary(v_clob, true, dbms_lob.call);
execute immediate 'select object_name from user_objects#' ||p_dblink
|| ' where data_object_id = dbms_rowid.rowid_object(:bv_rid) '
into v_remote_table using cast (p_rid as rowid);
loop
execute immediate
'select dbms_lob.substr#' ||p_dblink|| '(' ||p_clob_col|| ', ' ||c_chunk_size
|| ', ' ||v_pos|| ') from ' ||v_remote_table|| '#' ||p_dblink|| ' where rowid = :rid '
into v_chunk using p_rid;
begin dbms_lob.append(v_clob, v_chunk);
exception when others then
if sqlcode = -6502 then exit; else raise; end if;
end;
if length(v_chunk) < c_chunk_size then exit; end if;
v_pos := v_pos + c_chunk_size;
end loop;
return v_clob;
end dblink_clob;
I think the example is fairly self explanatory, but here's a bit of a description.
The function relies on the fact that you can call functions/procedures on a remote DB across a dblink - in this case dbms_lob.substr().
First it finds the remote table name by using its object id (which is encoded into the rowid). That saves having to pass the remote table name in as another parameter.
Note also that the p_rid parameter is a urowid, as it's a rowid from the remote DB. That's why it needs to be cast.
Then the CLOB is extracted and rebuilt in 4000 byte chunks, which is the maximum varchar2 size in PL/SQL. These chunks are varchar2's and can be passed across the dblink.
The if length(v_chunk) < c_chunk_size ... clause is satisfied after the last of the CLOB has been read (then the "chunk" buffer won't be filled).
The exception catch for ORA-06502 is needed when the length of the CLOB is a multiple of 4000, then the "if" clause is not satisfied, even though there is no more data.
You could just rely on this catch and remove the "if" clause altogether. But I did some performance testing and found that in most cases it's better to leave it in.
Howd probably had the correct way to do it 4 years ago, but I needed to update his to something like the following to get it to work (I simplified his for my own use, so the following might not compile or work, but you get the idea, the first query isn't needed anymore in 12.1.0.2.0):
create or replace function dblink_clob(
p_dblink in varchar2
, v_remote_table in varchar2
, p_clob_col in varchar2
, p_rid in urowid
)
return clob is
/** A function to fetch a CLOB column over a dblink **/
/** Laurence Prescott 25-Aug-17 **/
/** select dblink_clob('some_dblink', 'some_clob_column', rowid)
from some_table#some_dblink;
Note: Does not work on tables that have a virtual column (eg. xmltype).
**/
c_chunk_size constant pls_integer := 4000;
v_chunk varchar2(4000);
v_clob clob;
v_pos pls_integer := 1;
begin
dbms_lob.createtemporary(v_clob, true, dbms_lob.call);
loop
execute immediate
'select dbms_lob.substr#' ||p_dblink|| '(' ||p_clob_col|| ', ' ||c_chunk_size
|| ', ' ||v_pos|| ') from ' ||v_remote_table|| '#' ||p_dblink|| ' where rowid = :rid '
into v_chunk using p_rid;
begin dbms_lob.append(v_clob, v_chunk);
exception when others then
if sqlcode = -6502 then exit; else raise; end if;
end;
if length(v_chunk) < c_chunk_size then exit; end if;
v_pos := v_pos + c_chunk_size;
end loop;
return v_clob;
end dblink_clob;
The stored procedures being written here currently concats the parameters to the queries:
SELECT *
FROM Names
WHERE Name = ' || prmName || '
ORDER BY ' || prmSortField
Is it possible to parameterize this query inside the stored procedure? Possibly like:
query = 'select * From Names Where Name = #name Order By ' || prmSortField
call(query, prmName)
Note: In case you wonder why we do so, there are two common parameters for our sp's: sortFieldIndex and sortDirection. Since we cannot directly parameterize these, the query is dynamically generated. But other parameters make the queries open for injection. So I am looking a way to parameterize some of the parameters.
Absolutely. Use cursors.
DECLARE
CURSOR c1 (job VARCHAR2, max_wage NUMBER) IS
SELECT * FROM employees WHERE job_id = job AND salary > max_wage;
BEGIN
FOR person IN c1('CLERK', 3000)
LOOP
-- process data record
DBMS_OUTPUT.PUT_LINE('Name = ' || person.last_name || ', salary = ' ||
person.salary || ', Job Id = ' || person.job_id );
END LOOP;
END;
For a dynamic query with bind values, do this:
procedure p (prmName varchar2, prmSortField varchar2)
is
query varchar2(100);
rc sys_refcursor;
names_rec names%rowtype;
begin
query = 'select * From Names Where Name = :name Order By ' || prmSortField
open rc for query using prmName;
loop
fetch rc into names_rec;
exit when rc%notfound;
-- process this row
end loop;
close rc;
end;
For a more elaborate procedure that supports optional parameter values (but uses sys context), check out the following post on Asktom.com
PRATTY -- Thanks for the question regarding 'CURSOR'...