hi i'm newb in pl/sql :), this is for educational pruposes only.
the schama Dispatching including a table named Employes.and PRF schema that include a table named ZONE.
Dispatching : Employes(num_emp number,name nvarchar2,design_unit varchar2,design_zone varchar2)
and PRF: ZONE(num_zone number,design_zone varchar2,number_of_units number).
the problema is writing a pl/sql procedure to populate ZONE table from Employes table. this is my procedure :
create or replace procedure zoneD as
cursor cur is select design_zone,design_unit from dispatching.employes group by design_zone,design_unit;
varzone cur%rowtype;
begin
open cur;
fetch cur into varzone;loop
exit when cur%notfound;
insert into zone(num_zone,design_zone,nbr_of_unit) values (num_zone.nextval,varzone.design_zone,0);
update zone set nbr_of_unit =( select count(design_unit) from dispatching.employes);
end loop;
close cur;
end zoneD;
the unit is a town , each zone contains many units. in a simple way the prob the procedure does not insert the data i dont know if it is the right way to do that. (sorry about my english :)).
It seems that you are connected as PRF and want to fetch values that belong to DISPATCHING user. In order to do that, DISPATCHING has to grant (at least) SELECT on its EMPLOYEES table to PRF:
-- connect as DISPATCHING
grant select on employees to prf;
A procedure (as you're practicing PL/SQL) should utilize a cursor FOR loop as it is much easier to maintain than a loop which uses explicitly declared cursor (as you don't need to declare it as well as variable(s) you need to store its values into), open it, worry when to exit the loop and - finally - close it. Cursor FOR loop does all of that for you (OK, except writing a SELECT statement which is just the same as the one you'd use while declaring an explicit cursor).
-- connect as PRF
create or replace procedure zoned as
begin
-- cursor FOR loop - you can select both DESIGN_ZONE and count number of
-- units so that you wouldn't have to update that value separately
for cur_r in (select e.design_zone, count(*) number_of_units
from dispatching.employees e -- naming the owner which granted SELECT on its table to PRF user
group by e.design_zone
)
loop
insert into zone (num_zone, design_zone, number_of_units)
values (num_zone.nextval, cur_r.design_zone, cur_r.number_of_units);
end loop;
end;
/
That should do it (unless I made a typo).
Finally, a suggestion, if I may: do format your code properly. The one you posted is a mess difficult to read - no indentation, too long lines (break them!), and it contains only several lines. Imagine what happens when you have thousands of lines of code - who do you expect to debug it? Just a month or two after you've done with that code, you'll forget what you did and why (so comment it), and - if it is unformatted - you'll get a headache. Today's GUI tools offer automatic formatting, so - use it. Otherwise, there are free online formatters, such as Instant SQL Formatter.
Related
We are iterating 100k+ records from global temporary table.below stored procedure will iterate all records from glogal temp table one by one and has to process below three steps.
to see whether product is exists or not
to see whether product inside the assets are having the 'category' or not.
to see whether the assets are having file names starts with '%pdf%' or not.
So each record has to process these 3 steps and final document names will be stored in the table for the successful record. If any error comes in any of the steps then error message will be stored for that record.
Below stored procedure is taking long time to process Because its processing sequentially.
Is there any way to make this process faster in the stored procedure itself by doing batch process?
If it's not possible in stored procedure then can we change this code into Java and run this code in multi threaded mode? like creating 10 threads and each thread will take one record concurrently and process this code. I would be happy if somebody gives some pseudo code.
which approach is going to suggest?
DECLARE
V_NODE_ID VARCHAR2(20);
V_FILENAME VARCHAR2(100);
V_CATEGORY_COUNT INTEGER :=0;
FINAL_FILNAME VARCHAR2(2000);
V_FINAL_ERRORMESSAGE VARCHAR2(2000);
CURSOR C1 IS
SELECT isbn FROM GT_ADD_ISBNS GT;
CURSOR C2(v_isbn in varchar2) IS
SELECT ANP.NODE_ID NODE_ID
FROM
table1 ANP,
table2 ANPP,
table3 AN
WHERE
ANP.NODE_ID=AN.ID AND
ANPP.NODE_ID=ANP.NODE_ID AND
AN.NAME_ID =26 AND
ANP.CATEORGY='category' AND
ANP.QNAME_ID='categories' AND
ANP.NODE_ID IN(SELECT CHILD_NODE_ID
FROM TABLE_ASSOC START WITH PARENT_NODE_ID IN(v_isbn)
CONNECT BY PRIOR CHILD_NODE_ID = PARENT_NODE_ID);
BEGIN
--Iterating all Products
FOR R1 IN C1
LOOP
FINAL_FILNAME :='';
BEGIN
--To check whether Product is exists or not
SELECT AN.ID INTO V_NODE_ID
FROM TABLE1 AN,
TABLE2 ANP
WHERE
AN.ID=ANP.NODE_ID AND
ANP.VALUE in(R1.ISBN);
V_CATEGORY_COUNT :=0;
V_FINAL_ERRORMESSAGE :='';
--To check Whether Product inside the assets are having the 'category' is applied or not
FOR R2 IN C2(R1.ISBN)
LOOP
V_CATEGORY_COUNT := V_CATEGORY_COUNT+1;
BEGIN
--In this Logic Product inside the assets have applied the 'category' But those assets are having documents LIKE '%pdf%' or not
SELECT ANP.STRING_VALUE into V_FILENAME
FROM
table1 ANP,
table2 ANPP,
table3 ACD
WHERE
ANP.QNAME_ID=21 AND
ACD.ID=ANPP.LONG_VALUE
ANP.NODE_ID=ANPP.NODE_ID AND
ANPP.QNAME_ID=36 AND
ANP.STRING_VALUE LIKE '%pdf%' AND
ANP.NODE_ID=R2.NODE_ID;
FINAL_FILNAME := FINAL_FILNAME || V_FILENAME ||',';
EXCEPTION WHEN
NO_DATA_FOUND THEN
V_FINAL_ERRORMESSAGE:=V_FINAL_ERRORMESSAGE|| 'Category is applied for this Product But for the asset:'|| R2.NODE_ID || ':Documents[LIKE %pdf%] were not found ;';
UPDATE GT_ADD_ISBNS SET ERROR_MESSAGE= V_FINAL_ERRORMESSAGE WHERE ISBN= R1.ISBN;
END;--Iterating for each NODEID
END LOOP;--Iterating the assets[Nodes] for each product of catgeory
-- DBMS_OUTPUT.PUT_LINE('R1.ISBN:' || R1.ISBN ||'::V_CATEGORY_COUNT:' || V_CATEGORY_COUNT);
IF(V_CATEGORY_COUNT = 0) THEN
UPDATE GT_ADD_ISBNS SET ERROR_MESSAGE= 'Category is not applied to none of the Assets for this Product' WHERE ISBN= R1.ISBN;
END IF;
EXCEPTION WHEN
NO_DATA_FOUND THEN
UPDATE GT_ADD_ISBNS SET ERROR_MESSAGE= 'Product is not Found:' WHERE ISBN= R1.ISBN;
END;
-- DBMS_OUTPUT.PUT_LINE( R1.ISBN || 'Final documents:'||FINAL_FILNAME);
UPDATE GT_ADD_ISBNS SET FILENAME=FINAL_FILNAME WHERE ISBN= R1.ISBN;
COMMIT;
END LOOP;--looping gt_isbns
END;
You have a number of potential performance hits. Here's one:
"We are iterating 100k+ records from global temporary table"
Global temporary tables can be pretty slow. Populating them means writing all that data to disk; reading from them means reading from disk. That's a lot of I/O which might be avoidable. Also, GTTs use the temporary tablespace so you may be in contention with other sessions doing large sorts.
Here's another red flag:
FOR R1 IN C1 LOOP
... FOR R2 IN C2(R1.ISBN) LOOP
SQL is a set-based language. It is optimised for joining tables and returning sets of data in a highly-performative fashion. Nested cursor loops mean row-by-row processing which is undoubtedly easier to code but may be orders of magnitude slower than the equivalent set operation would be.
--To check whether Product is exists or not
You have several queries selecting from the same tables (AN, 'ANP) using the same criteria (isbn`). Perhaps all these duplicates are the only way of validating your business rules but it seems unlikely.
FINAL_FILNAME := FINAL_FILNAME || V_FILENAME ||',';
Maybe you could rewrite your query to use listagg() instead of using procedural logic to concatenate a string?
UPDATE GT_ADD_ISBNS
Again, all your updates are single row operations instead of set ones.
"Is there any way to make this process faster in the stored procedure itself by doing batch process?"
Without knowing your rules and the context we cannot rewrite your logic for you, but 15-16 hours is way too long for this so you can definitely reduce the elapsed time.
Things to consider:
Replace the writing and reading to the temporary table with the query you use to populate it
Rewrite the loops to use BULK COLLECT with a high LIMIT (e.g. 1000) to improve the select efficiency. Find out more.
Populate arrays and use FORALL to improve the efficiency of the updates. Find out more.
Try to remove all those individual look-ups by incorporating the logic into the main query, using OUTER JOIN syntax to test for existence.
These are all guesses. If you really want to know where the procedure is spending the time - and that knowledge is the root of all successful tuning, so you ought to want to know - you should run the procedure under a PL/SQL Profiler. This will tell you which lines cost the most time, and those are usually the ones where you need to focus your tuning effort. If you don't already have access to DBMS_PROFILER you will need a DBA to run the install script for you. Find out more.
" can we change this code into Java and run this code in multi threaded mode?"
Given that one of the reasons for slowing down the procedure is the I/O cost of selecting from the temporary table there's a good chance multi-threading might introduce further contention and actually make things worse. You should seek to improve the stored procedure first.
How do I perform a select on a rather simple view in oracle pl/sql using a stored procedure.
Lets say the view looks like this:
FirstName LastName
-------- -------
Bob Jones
James Kay
etc...
To me its should be so simple:
Procedure SuperSimple()
begin
select FirstName, LastName from SuperSimple
end
However I've been told that this will not work.
So I tried to use a PL/SQL cursor. Still scratching my head trying to figure out why I am using cursors. But it appears to be necessary in 11g.
Procedure AlphaPrime(Results OUT Ref CURSOR) IS
begin
OPEN Results for
select FirstName, LastName from SuperSimple;
end;
Now I was hoping this would work but I'm doing something like this with select statements and it appears to be not working.
Do I also need to add a fetch and another open and a close command to make this thing work? What is the idea behind all this? I've noticed that trying to find info on how to add a very simple select statemetn to a procedure appears to be missing from most documentation that I've read. Is there a reason for this like its too simple to add a select statement to a procedure as it would be better to add it to a view. Something along those lines.
The problem I'm having is I want to start out really simple and tac on a little bit more complexity to the sproc over time... where time ~ 1 to 2 hours. Can someone point me to some docs in Oracle PL/SQL that shows how to add a simple table or view. Also If the permissions for a specific view or table is not allowed does it just fail for that user or does it give an empty result set.
It is not clear from your question what are you intending to do with the query result inside your procedure. So here I make some examples with dbms_output which prints to screen out some message and data from your query. Probably you will replace it with your logic.
Let's have some view (actually it doesn't matter here whether you are querying view or table, but I would stick to your question)
create table some_simple_table(firstname varchar2(30), lastname varchar2(30));
/
create or replace view supersimple_view as select firstname, lastname, 'whatever1' whatever from some_simple_table;
/
The following code does select into variable, this will work only if query returns exactly one row.
create or replace procedure supersimple1 is
vfirstname supersimple_view.firstname%type;
vwhatever supersimple_view.whatever%type;
vsupersimple supersimple_view%rowtype;
begin
select firstname, whatever into vfirstname, vwhatever from supersimple_view;
dbms_output.put_line('I''m doing some logic with this'|| vwhatever );
select * into vsupersimple from supersimple_view;
dbms_output.put_line('I''m doing some logic with this'|| vsupersimple.firstname);
end;
/
Perhaps you can implement implicit cursor loop through results and do some logic.
create or replace procedure supersimple2 is
begin
for rec in (select * from supersimple_view)
loop
dbms_output.put_line('I''m doing some logic with this record '|| rec.firstname);
end loop;
end;
/
Another option is cursor (particularly in case when you will reuse the same select) loop through results and do some logic.
create or replace procedure supersimple3 is
cursor cur is (select * from supersimple_view);
vsupersimple cur%rowtype;
begin
open cur ;
loop
FETCH cur INTO vsupersimple;
EXIT WHEN cur%NOTFOUND;
dbms_output.put_line('I''m doing some logic with this record '|| vsupersimple.firstname);
end loop;
close cur;
end;
/
You can fetch result of your query to collection
create or replace procedure supersimple4 is
type supersimple_colt is table of supersimple_view%rowtype index by pls_integer;
vsupersimple_col supersimple_colt;
begin
select * bulk collect into vsupersimple_col from supersimple_view ;
for i in 1 .. vsupersimple_col.count
loop
dbms_output.put_line('I''m doing some logic with this record '|| vsupersimple_col(i).firstname);
end loop;
end;
/
Instead of PL/SQL type declared in supersimple4 you can create standalone database SQL types and used them to fetch results into. This aproach gives you various features like: possibility to query collection in select statement in table like fashion, converting it to xml by xmltype, etc.
I think I found the answer. For each column that is selected on, it needs a view or table column type, which is sort of like the list of parameters used for the final output. That way when you declare on it you can better know what you are getting, which sorta makes sense.
So if you have two tables or views which were used to generate the output columns, you would need both of those tables or views in your descriptive OUT variables to describe better what you are outputting in the final output result.
See this link.
I'm taking an educated guess with this next part as I'm just beginning to understand it:
This query should work. But if its not it may be due to insuffiecient priviledges. Try a table that you know you have access and select it in a procedure in debug mode. Then try a view.
Procedure AlphaPrime(Results OUT Ref CURSOR) IS
begin
OPEN Results for
select FirstName, LastName from SuperSimple;
end;
Also there is a possibility with Debug mode and your assigned user roles that you may have insufficient priviledges to debug all the objects in the view and they may not be accessible. Sometimes you can just hit the "Ignore" button in Toad to skip over debugging inside a stored procedure. Also you may have priveledges to view the results the object just not view its structure which may also give you insufficient priviledges errors. Again just ignore them to skip over those types of issues and see the results while in debug mode. If you don't debug this, then you should not see any errors and just get the results.
I'm trying to make a Procedure in PL/SQL for a ticket system for my website.
I want a simple procedure that saves some data into the database.
It will have name of the user (random VARCHAR2) and a number and the date of today as input.
When this procedure is called it will automatically higher a number in the database (Maybe primary key) and add the input into the database. But I have no idea how to make something like this with the option to have more people access it at once.
Can anyone help please?
For the people that want to know what I have already.
CREATE OR REPLACE PROCEDURE VoegBoeteToe
(v_speler IN number, v_date IN date, v_boete IN number)
IS VoegBoeteToe
DECLARE
v_nextNum NUMBER;
BEGIN
SELECT BETALINGSNR
INTO v_nextNum
FROM BOETES
ORDER BY BETALINGSNR DESC;
v_nextNum := v_nextNum + 1;
INSERT INTO BOETES VALUES(v_nextNum,v_speler,v_date,v_boete);
end;
/
The fourth line of your procedure should be IS instead of IS VoegBoeteToe. In addition, remove the fifth line (DECLARE), which is not needed in a procedure. And use a sequence to derive the sequential number safely.
So, when all's said and done, your procedure should look something like:
CREATE SEQUENCE BOETES_SEQ;
CREATE OR REPLACE PROCEDURE VoegBoeteToe
(v_speler IN number, v_date IN date, v_boete IN number)
IS
BEGIN
INSERT INTO BOETES VALUES(BOETES_SEQ.NEXTVAL,v_speler,v_date,v_boete);
end;
It would also be a very good idea to include the list of field names in the BOETES table into which you're inserting values. This will make life better for anyone who has to maintain your code, and may save you from making a few errors.
Share and enjoy.
You need to create SEQUENCE
CREATE SEQUENCE DUMMY_SEQ
/
Then in Your code You can use something like that:
select DUMMY_SEQ.NEXTVAL into myVar from dual;
First, create a sequence. A sequence generates an ever incrementing number whenever it is invoked. See this for some nice examples. The documentation could be useful too.
CREATE SEQUENCE user_sequence
START WITH 1000 -- your sequence will start with 1000
INCREMENT BY 1; -- each subsequent number will increment by 1
Then,
SELECT user_sequence.NEXTVAL INTO v_nextNum FROM DUAL;
I'd like to return multiple cursor in one procedure, one based on other.
My currently code is:
TYPE REFCURSOR IS REF CURSOR;
PROCEDURE GETCARS(oCARS OUT REFCURSOR)
BEGIN
OPEN oCARS FOR SELECT * FROM CARS;
END GETCARS;
I'm not sure if that's posible but I want to make something like:
PROCEDURE GETCARS(oCARS OUT REFCURSOR, oREPAIRS OUT REFCURSOR)
BEGIN
OPEN oCARS FOR SELECT * FROM CARS;
..??..
END GETCARS;
which would return as a second parameter all repairs connected with currently fetched oCARS row.
(Table repairs has a FK for an id_car from cars)
Now I do that on C# side, when I fetch one row from oCARS cursor I call second procedure which gives me list of repairs, but maybe it's somehow possible to do that in one procedure (which would give me performance gain? - I don't want to use join, cause it returns multiplied cars for each repair)
The simple answer is that you can't do what you are attempting.
Cursors are basically just pointers to the start of the result set that contains the query results. Until you fetch a row, there is no way to know what that row will contain. Because your application, rather than the PL/SQL code, is doing the fetching, the PL/SQL portion has no knowledge of the values being returned.
To do what you're attempting, the database would have to detect the fetch from the first query, create a new result set using the second query, then place the new result set at the address that the procedure originally returned for the second cursor. Databases just aren't designed to handle this kind of operation.
How about
PROCEDURE GETCARS(oCARS OUT SYS_REFCURSOR, oREPAIRS OUT SYS_REFCURSOR, oCHARGES OUT SYS_REFCURSOR)
BEGIN
OPEN oCARS FOR SELECT * FROM CARS;
OPEN oREPAIRS FOR SELECT * FROM REPAIRS;
OPEN oCHARGES FOR SELECT * FROM CHARGES;
END GETCARS;
Share and enjoy.
I am trying to use the following procedure as a datasource for my crystal report. The query works as I expected but the problem is I can't figure out how to fetch back the data from those dummy tables - IFS_PR_DUMMY_TAB and IFS_PR_DUMMY2_TAB
CREATE OR REPLACE procedure dummy9_IFS_FR2_Sales (cdate IN date)
as
acontract customer_order.contract%type;
ashowroom customer_order.district_code%type;
aorderno customer_order.order_no%type;
amount number(10);
bcontract customer_order.contract%type;
bshowroom customer_order.district_code%type;
borderno customer_order.order_no%type;
bamount number(10);
CURSOR c2 IS
select contract, district_code ,count(order_no),
SUM(CUSTOMER_ORDER_API.Get_Total_Sale_Price__(order_no))
from CUSTOMER_ORDER
where order_no IN (select distinct order_no from customer_order_line where state IN ('Released') ) AND state IN ('Released') and to_char(date_entered,'MM/DD/YYYY')>=to_char(cdate,'MM/DD/YYYY')
and contract IN
('CERA','SAN','WOD','QEM','PIP','COT','KIT','MAR','PROJ')
group by contract,district_code, date_entered ;
CURSOR c2 IS
select contract, district_code ,count(order_no),
SUM(CUSTOMER_ORDER_API.Get_Total_Sale_Price__(order_no))
from CUSTOMER_ORDER
where order_no IN (select distinct order_no from customer_order_line where state IN ('Reserved') ) AND state IN ('Reserved') and to_char(date_entered,'MM/DD/YYYY')>=to_char(cdate,'MM/DD/YYYY')
and contract IN
('CERA','SAN','WOD','QEM','PIP','COT','KIT','MAR','PROJ')
group by contract,district_code, date_entered ;
begin
--For Released Orders
OPEN c1;
DELETE FROM IFS_PR_DUMMY_TAB;
loop
fetch c1 into acontract, ashowroom, aorderno, amount;
exit when c1%notfound;
Insert into IFS_PR_DUMMY_TAB
(DCONTRACT ,DSHOWROOM ,DORDERNO,DAMOUNT) values (acontract,ashowroom,aorderno,amount);
end loop;
close c1;
--For Reserved Orders
OPEN c2;
DELETE FROM IFS_PR_DUMMY2_TAB;
loop
fetch c2 into bcontract, bshowroom, borderno, bamount;
exit when c2%notfound;
Insert into IFS_PR_DUMMY2_TAB
(ECONTRACT ,ESHOWROOM ,EORDERNO,EAMOUNT) values (bcontract,bshowroom,borderno,bamount);
end loop;
close c2;
end;
The best way to solve your problem is to have your procedure return result sets. In Oracle we use REF CURSORS to achieve this. You don't need to populate the temporary tables any more, but we can use one of them to define the signature of the REF CURSOR.
create or replace package report_records as
type order_recs is ref cursor
return IFS_PR_DUMMY_TAB%rowtype;
end;
/
This procedure returns two ref cursors.
create or replace procedure dummy9_ifs_fr2_sales
(cdate in date
, c_released_orders in out report_records.order_recs
, c_reserved_orders in out report_records.order_recs
)
begin
open c_released_orders for
select contract
, district_code
,count(order_no)
,sum(customer_order_api.get_total_sale_price__(order_no))
from customer_order
where order_no
in (select distinct order_no
from customer_order_line
where state in ('Released') )
AND state in ('Released')
and to_char(date_entered,'MM/DD/YYYY')>=to_char(cdate,'MM/DD/YYYY')
and contract in ('CERA','SAN','WOD','QEM','PIP','COT','KIT','MAR','PROJ')
group by contract,district_code, date_entered ;
open c_released_orders for
select contract
, district_code
,count(order_no)
,sum(customer_order_api.get_total_sale_price__(order_no))
from customer_order
where order_no in (select distinct order_no
from customer_order_line
where state in ('Reserved') )
AND state in ('Reserved')
and to_char(date_entered,'MM/DD/YYYY')>=to_char(cdate,'MM/DD/YYYY')
and contract in ('CERA','SAN','WOD','QEM','PIP','COT','KIT','MAR','PROJ')
group by contract,district_code, date_entered ;
end;
/
As a matter of interest, if your date_entered column is a DATE datatype then you shouldn't use the TO_CHAR() conversion. If you are looking to handle rows which have a time element there are more efficient ways of handling that.
Ref Cursors are explained in detail in the Oracle PL/SQL User's Guide. Find out more.
edit
I'm not a Crystal Reports person. Google only seems to throw out some pretty old documentation (like this). But the consensus seems to be that CR is pretty restricted when it comes to interacting with Oracle stored procedures.
Apparently Crystal Reports needs the parameters declared as IN OUT. Also it appears it can only handle one such ref cursor parameter. Furthermore the ref cursor needs to be the first argument in the procedure's signature. Finally, and to my mind completely incredibly, the "stored procedure cannot call another stored procedure." We are used to design patterns which state that calling programs shouldn't have to know anything about the internals of the called program, but here we seem to have the internal workings of a called program being determined by the sort of program which calls it. That's pretty lame.
So, anyway, the above solution won't work for Crystal Reports. The only solution is to break it up into two procedures, with signatures like this:
create or replace procedure dummy9_ifs_fr2_sales_released
(c_released_orders in out report_records.order_recs
, cdate in date
)
as ...
create or replace procedure dummy9_ifs_fr2_sales_reserved
(c_reserved_orders in out report_records.order_recs
, cdate in date
)
as ...
These procedures could be bundled into a package (assuming CR can cope with that construct).
If the two procedure solution is not acceptable then I think you're left with David's approach: abandon stored procedures altogether, and just use raw SQL in the report.
Your code sucks.
Firstly, why are you using explicit cursors? Why wouldn't you just insert the rows into the tables?
Secondly, why delete when you could truncate much faster?
Thirdly, to_char(date_entered,'MM/DD/YYYY')>=to_char(cdate,'MM/DD/YYYY') applies a function to a column (so an index can't be used and the optimiser cannot get a good estimate of cardinality), and it converts the dates to a stupid character format with the month in the leading position so that it does not even do a correct comparison! 02-november-2009 sorts greater than 01-march-2010 in your logic.
Fourthly, why on earth are you using a stored procedure for this? Just run the damn queries and Union All them together if you need to.
This reminds me of all of the crap I saw from offshore report developers for two years at my previous job. Complete incompetence.