Reading IN parameters in a query in PL/SQL - oracle

I have written a PL/SQL procedure as:
CREATE OR REPLACE PROCEDURE checkProdQuantity (productid IN number, orderqty IN number)
IS
qty number;
qty_diff number;
BEGIN
SELECT quantity INTO qty from Products where ProductID=productid;
IF orderqty>qty THEN
dbms_output.put_line('Ordered quatity is greater than available quantity');
ELSE
qty_diff:=qty-orderqty;
UPDATE Products set quantity=qty_diff where ProductID=productid;
END IF;
END;
/
But when I try to execute this procedure with valid parameters, it shows an error: exact fetch returns more than the requested number of rows.
I have checked my table, and for the parameters I am supplying it should return only one row. I think for some reason, the value of productid IN parameter is not being read in the select query. Even if I provide some random values for productid parameter, it still gives the same error. I am unable to figure out where the problem is.

I don't think the compiler is distinguishing between the parameter productid and the column name ProductID.
Try renaming your parameter to a_productId, or something different from the column name.

Its happning because, when you write
SELECT quantity INTO qty from Products where ProductID=productid;
Oracle scope resolution interprets productid as the column_name and not as your input variable.
Change the name of the input variable to something other than the column name and it should work.
Hope it helps
Vishad

Related

PL/SQL reusable dynamic sql program for same type of task but different table and column

Thank you for reply guys. I kind of solved my problem.
I used to try to update data with ref cursor in dynamic SQL using "where current of" but I now know that won't work.
Then I tried to use %rowtype to store both 'id' and 'clob' in one variable for future updating but turns out weak ref cursor can't use that type binding either.
After that I tried to use record as return of an ref cursor and that doesn't work on weak cursor either.
On the end, I created another cursor to retrieve 'id' separately along with cursor to retrieve 'clob' on the same time then update table with that id.
I'm now working on a Oracle data cleaning task and have a requirement like below:
There are 38 tables(maybe more in the future) and every table has one or multiple column which type is Clob. I need to find different keyword in those columns and according to a logic return binary label of the column and store it in a new column.
For example, there is a table 'myTable1' which has 2 Clob columns 'clob1' and 'clob2'. I'd like to find keyword 'sky' from those columns and store '0'(if not found) or '1'(if found) in two new columns 'clob1Sky','clob2Sky'.
I know if I could write it on a static way which will provide higher efficiency but I have to modify it for those very similar tasks every time. I want save some time on this so I'm trying to write it in a reusable way and not binding to certain table.
But I met some problem when writing the program. My program is like below:
create or replace PACKAGE body LABELTARGETKEYWORD
as
/**
#param varcher tableName: the name of table I want to work on
#param varchar colName: the name of clob column
#param varchar targetWord: the word I want to find in the column
#param varchar newColName: the name of new column which store label of clob
*/
PROCEDURE mainProc(tableName varchar, colName varchar,targetWord varchar,newColName varchar2)
as
type c_RecordCur is ref cursor;
c_sRecordCur c_recordCur;
/*other variables*/
begin
/*(1) check whether column of newColName exist
(2) if not, alter add table of newColName
(3) open cursor for retrieving clob
(4) loop cursor
(5) update set the value in newColName accroding to func labelword return
(6) close cursor and commit*/
end mainProc;
function labelWord(sRecord VARCHAR2,targetWord varchar2) return boolean...
function ifColExist(tableName varchar2,newColName varchar2) return boolean...
END LABELTARGETKEYWORD;
Most DML and DDL are written in dynamic sql way.
The problem is when I write the (5) part, I notice 'Where current of' clause can not be used in a ref cursor or dynamic sql statement. So I have to change the plan.
I tried to use a record(rowid,label) to store result and alter the table later.(the table only be used by two people in my group, so there won't be problem of lock and data changes). But I find because I'm trying to use dynamic sql so actually I have to define ref cursor with return of certain %rowtype and basically all other variables, %type in dynamic sql statement. Which makes me feel my method has something wrong.
My question are:
If there a way to define %type in dynamic sql? Binding type to variable in dynamic SQL?
Could anybody give me a hint how to write that (5) part in dynamic SQL?
Should not I design my program like that?
Is it not the way how to use dynamic SQL or PLSQL?
I'm very new to PL/SQL. Thank you very much.
According to Tom Kyte's advice, to do it in one statement if it can be done in one statement, I'd try to use a single UPDATE statement first:
CREATE TABLE mytable1 (id NUMBER, clob1 CLOB,
clob2 CLOB, clob1sky NUMBER, clob2sky NUMBER )
LOB(clob1, clob2) STORE AS SECUREFILE (ENABLE STORAGE IN ROW);
INSERT INTO mytable1(id, clob1, clob2)
SELECT object_id, object_name, object_type FROM all_objects
WHERE rownum <= 10000;
CREATE OR REPLACE
PROCEDURE mainProc(tableName VARCHAR2, colName VARCHAR2, targetWord VARCHAR2, newColName VARCHAR2)
IS
stmt VARCHAR2(30000);
BEGIN
stmt := 'UPDATE '||tableName||' SET '||newColName||'=1 '||
'WHERE DBMS_LOB.INSTR('||colName||','''||targetWord||''')>1';
dbms_output.put_line(stmt);
EXECUTE IMMEDIATE stmt;
END mainProc;
/
So, calling it with mainProc('MYTABLE1', 'CLOB1', 'TAB', 'CLOB1SKY'); fires the statement
UPDATE MYTABLE1 SET CLOB1SKY=1 WHERE DBMS_LOB.INSTR(CLOB1,'TAB')>1
which seems to do the trick:
SELECT * FROM mytable1 WHERE clob1sky=1;
id clob1 clob2 clob1sky clob2skiy
33 I_TAB1 INDEX 1
88 NTAB$ TABLE 1
89 I_NTAB1 INDEX 1
90 I_NTAB2 INDEX 1
...
I am not sure with your question-
If this job is suppose to run on daily or hourly basis ,running query through it will be very costly. One thing you can do - put all your clob data in a file and save it in your server(i guess it must be linux). then you can create a shell script and schedule a job to run gerp command and fetch your required value and "if found then update your table".
I think you should approaches problem another way:
1. Find all columns that you need:
CURSOR k_clobs
select table_name, column_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
Or 2 cursor(you can build you query if you have more than 1 CLOB per table:
CURSOR k_clobs_table
select DISTINCT table_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
CURSOR k_clobs_columns(table_namee varchar(255)) is
select column_name from dba_tab_cols where data_type in ('CLOB','NCLOB') and table_name = table_namee;
Now you are 100% that column you are checking is clob, so you don't have to worry about data type ;)
I'm not sure what you want achieve, but i hope it may help you.

Unable to retrieve multiple records in an Oracle extract

I need to get the year from a date in all of the records in an Oracle database. The following is my attempt:
CREATE or replace TYPE BODY student_t AS MEMBER FUNCTION getYear RETURN NUMBER IS
yearDOB NUMBER;
BEGIN
SELECT EXTRACT(YEAR FROM s.dob) INTO yearDOB
from student s;
return yearDOB;
END;END;/
But that will not work when the table has more than 1 record.
How I can fix the issue?
When you do select into rdbms thinks that only one value will be returned, or that your variable will be an array. You cannot set a NUMBER variable to an array of objects.

member function to get the sum in oracle

I have a type called sell_type defined as
CREATE OR REPLACE TYPE sell_type AS OBJECT (
dname VARCHAR (50),
car_model VARCHAR(20),
make VARCHAR (20),
price NUMBER (10,2),
MEMBER FUNCTION total_sales RETURN NUMBER
);
/
Body:
CREATE OR REPLACE TYPE BODY sell_type AS
MEMBER FUNCTION total_sales RETURN NUMBER IS
BEGIN
RETURN SELF.price;
END total_sales;
END;
/
And an object table
CREATE TABLE sell of Sell_Type;
/
I want to get the total sales for a given seller with something like:
select s.total_sales() from sell s
where s.dname = 'John Doe';
But what I get is a separate list of prices of all the sales of that given seller, rather than the total of those prices.
I know that I have to fix my type body somehow. I tried to use the SUM() inside the return but that didn't work. Can someone please help?
Summing is an aggregation, a set function. A Type is a single thing; it is not possible for a Type instance to execute an aggregation across all the instances of its peers.
If you want to do such a thing you would need to declare a new type, with a signature like this:
CREATE OR REPLACE TYPE sell_set AS OBJECT (
sell_items sell_type,
MEMBER FUNCTION total_sales (p_seller varchar2) RETURN NUMBER
);
/
Writing the body for this type is left as an exercise for the reader ;-)
Note that Oracle SQL and PL/SQL do work with OO concepts but in a clunky fashion. It's fine to explore the syntax for educational purposes, if only to learn its limitations. But there are a very narrow set of use cases in real life. A relational data model is the far superior way of storing data.
This body will do the trick. But it will return the same value for each row on the sell table. Therefore, you have to use 'group by' or 'max()' if you want to see just one row of result.
CREATE OR REPLACE
TYPE BODY SELL_TYPE AS
MEMBER FUNCTION total_sales (p_seller varchar2) RETURN NUMBER IS
total_price NUMBER;
BEGIN
SELECT sum(s.price) INTO total_price FROM sell s where s.dname = p_seller;
RETURN total_price;
END total_sales;
END;
/
select query will look like this.
select s.total_sales('John')
from sell s GROUP BY s.total_sales('John');

Determining input datatype Oracle/PLSQL

I am writing a PLSQL 'INSTEAD OF INSERT' Trigger whereby the ID field (GID) can be inserted as either a string or a number. If the GID value is a string I would like to attempt to convert that into the correct GID (number) otherwise if a number is input the script will continue.
The part I am struggling with here is determining the datatype of ':New.CHART_GID' - is this possible in PLSQL? I can't check for chars in the string as the string may only contain numbers in some instances.
Thanks.
You can use TRANSLATE to check if there is something other as numbers:
CREATE OR REPLACE TRIGGER trigger_name
INSTEAD OF INSERT
ON table_name
FOR EACH ROW
DECLARE
vGID INTEGER;
...... other things
BEGIN
IF :New.CHART_GID is not null AND TRANSLATE(:New.CHART_GID,'0123456789',' ') is null THEN
vGID := TO_NUMBER(:New.CHART_GID);
.... do what you want with number
ELSE
... do what you want with not number
END IF;
.... other things
END;
CHART_GID have to be varchar2 in the view
I realise what I was trying to achieve was actually not possible. The solution for me was actually to join the Chart_no into the view and insert into either that field of the GID. If I input a Chart_no the GID field would be automatically populated and the same for if I input a GID.

"Invalid Cursor" error while running overloaded PLSQL stored procedure

I am creating an overloaded PLSQL stored procedure which allows to display the names of schools, their corresponding category (elementary, etc), and neighbourhood they belong to.
The names of schools is taken from table OTTAWASCHOOLS from the field NAME. The category is taken from the table OTTAWASCHOOLS from the field CATEGORY.
In addition, the user has the choice to input a particular neighbourhood to find the above information of the schools in that neighbourhood. The name of the neighbourhood is taken from the OTTAWANEIGHBOUR table from the field NAME.
However, if the user does NOT input a specific neighbourhood, the output will display the names ALL the schools in the OTTAWASCHOOLS table with their respective neighbourhoods and categories
(I have created only one procedure at the moment).
My code is as follows
SET SERVEROUTPUT ON;
SET VERIFY OFF
CREATE OR REPLACE PACKAGE schools_package
AS
PROCEDURE find_school
(neighbourhood_name IN OTTAWANEIGHBOUR.NAME%TYPE);
END schools_package;
/
CREATE OR REPLACE PACKAGE BODY schools_package
AS
PROCEDURE find_school
(neighbourhood_name IN OTTAWANEIGHBOUR.NAME%TYPE)
IS
school_category OTTAWASCHOOLS.CATEGORY%TYPE;
school_name OTTAWASCHOOLS.NAME%TYPE;
v_neighbourhood_name OTTAWANEIGHBOUR.NAME%TYPE;
CURSOR c_schools IS
SELECT NAME, CATEGORY
FROM eluliGDM.OTTAWASCHOOLS;
r_schools c_schools%ROWTYPE;
BEGIN
FOR r_schools IN c_schools
LOOP
SELECT c1.NAME, c2.NAME, c2.CATEGORY
INTO v_neighbourhood_name, school_name, school_category
FROM eluliGDM.OTTAWANEIGHBOUR c1, eluliGDM.OTTAWASCHOOLS c2
WHERE SDO_RELATE (c2.GEOMETRY, c1.GEOMETRY, 'MASK=INSIDE+COVEREDBY QUERYTYPE=JOIN') = 'TRUE'
AND c2.NAME=r_schools.NAME;
DBMS_OUTPUT.PUT_LINE ('NEIGHBOURHOOD ' || 'CATEGORY '|| 'SCHOOL NAME ');
DBMS_OUTPUT.PUT_LINE ('------------- ' || '-------- '|| '----------- ');
DBMS_OUTPUT.PUT_LINE (v_neighbourhood_name || school_category|| school_name);
END LOOP;
CLOSE c_schools;
END find_school;
END schools_package;
-----------TESTING STORED PROCEDURE---------------
Execute schools_package.find_school();
Execute schools_package.find_school('Mer Bleue');
But when I test the procedure, I get an error :01001. 00000 - "invalid cursor" then proceeds to show me ALL neighborhoods and their corresponding schools. What is wrong with my cursor?
Remove the CLOSE c_schools; statement. The Cursor For Loop already takes care of that. See Oracle Docs:
"The cursor FOR LOOP statement implicitly declares its loop index as a record variable of the row type that a specified cursor returns, and then opens a cursor. With each iteration, the cursor FOR LOOP statement fetches a row from the result set into the record. When there are no more rows to fetch, the cursor FOR LOOP statement closes the cursor."
According to your typing, OTTAWASCHOOLS contains both columns NAME and CATEGORY, so the cursor itself appears to be validly defined.
OTOH, does schema eluliGDM own both the tables and the package? If that is not the package owner, perhaps there are privilege issues? If the schema is the same, why specify the schema in the code? If not the same, consider the use of synonyms and removing the hard-coded schema from the code.
I'm not sure why you have an input parameter; you're not using it. So, I'm not surprised you're getting all the schools; the cursor has no predicate so it's the full table, and the SELECT inside the LOOP joins wherever the NAME column is the same in both tables. Without anything to limit based on input parameter, you have no filters at all beyond the join.
HTH

Resources