Is it possible to parameterize a query inside pl sql? - oracle

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'...

Related

Oracle pl/sql Array

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).

How do I fix my procedure country_demographics?

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);

Display a table inside an Oracle procedure

I'm on Oracle DB 12c and using SQL Developer.
How can I display every rows of a table inside a procedure, if all arguments are "null".
I'm guessing that my IF is correct and I might have seen two or three posts where they used cursors but I'm not very familiar with the utilization of those.
I would like it to basically be a SELECT * FROM Salle but with a condition.
Am I on the right track here ?
CREATE OR REPLACE PROCEDURE Foo1 (noSalle in varchar2, Cat in varchar2, Nb in number)
IS
cursor SYS_REFCURSOR; -- Not sure at all about that
BEGIN
DBMS_OUTPUT.PUT_LINE('Salle : ' || noSalle || 'Cat : ' || Cat);
IF (noSalle = null AND Cat = null AND Nb = null) THEN
OPEN cursor FOR
SELECT * from Salle;
-- Some sort of FOR row IN cursor LOOP ?
-- Display ALL rows of "Salle"
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Erreur Oracle : '||sqlcode||' ; Message Oracle : '||sqlerrm);
END;
SYS_REFCURSOR is used to hold tabular data usually used when you want to return such data from a function.
Null comparison is incorrect. Use colname is null instead.
If you just want to print all the rows using dbms_output.put_line or something similar, you can do this
for row in (select * from salle) loop
dbms_output.put_line(row.col1||' '||row.col2);
end loop;

oracle bulk collect and reading the data

I have created below proc to read all the data from one table and populate it in a grid in .net form.
CREATE OR REPLACE PROCEDURE EVMPDADM.GETALLBATCHES_ARTICLE_57(p_batchstatus OUT XEVMPD_SUBMITTEDBATCH%ROWTYPE )
IS
TYPE batch_status IS TABLE OF XEVMPD_SUBMITTEDBATCH%ROWTYPE INDEX BY PLS_INTEGER;
l_batchstatus batch_status;
BEGIN
SELECT * BULK COLLECT INTO l_batchstatus FROM XEVMPD_SUBMITTEDBATCH ;
FOR i IN 1..l_batchstatus.count LOOP
p_batchstatus:= l_batchstatus(i);
END LOOP;
END GETALLBATCHES_ARTICLE_57;
To test if the proc is running fine I tried to print the data by using below Pl-sql block:
DECLARE
v_batchstatus XEVMPD_SUBMITTEDBATCH%ROWTYPE;
BEGIN
EVMPDADM.GETALLBATCHES_ARTICLE_57(v_batchstatus);
DBMS_OUTPUT.PUT_LINE( v_batchstatus.Batch_id || ' ' || v_batchstatus.BATCH_DESCRIPTION || ' ' || v_batchstatus.STATUS || ' ' ||v_batchstatus.RECORD_STATUS || ' ' ||v_batchstatus.NUMBER_OF_RECORDS);
END;
/
But from this process I am getting the last row only.
I want to print all the records present in the table.
can any one please help me to figure out what is wrong in the above code.
The error messages are very obvious. You are calling your procedures with:
Wrong number of arguments for EVMPDADM.GETALLBATCHES_ARTICLE_57: It has one OUT parameter. So you need to pass that parameter.
Wrong type of argument for DBMS_OUTPUT.PUT_LINE: It has one IN VARCHAR2 parameter, and not XEVMPD_SUBMITTEDBATCH%ROWTYPE. Read here
So, it should be this way:
DECLARE
v_batchstatus XEVMPD_SUBMITTEDBATCH%ROWTYPE;
BEGIN
v_batchstatus:= EVMPDADM.GETALLBATCHES_ARTICLE_57(v_batchstatus);
--use DBMS_OUTPUT.PUT_LINE for every column of XEVMPD_SUBMITTEDBATCH separately after you convert them to varchar2 if they are not.
END;
/
Besides, the procedure this way will return only the last row. So you might want to change that.
If you want to print all the records from the table, you need to add DBMS_OUTPUT.PUT_LINE inside the loop, it will become like this:
FOR i IN 1..l_batchstatus.count LOOP
p_batchstatus:= l_batchstatus(i);
dbms_output.put_line( p_batchstatus.col1 || ' ' || p_batchstatus.col2 || ... );
END LOOP;
Where col1, col2, ... are the columns names of XEVMPD_SUBMITTEDBATCH given they are of the type VARCHAR2. Or you will need extra processing

Reusing PROCEDURE/ FUNCTION whichever to do the same thing using different values

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;

Resources