Not able to display average - plsql

i want to display an average score but its not getting displayed even though the code is executed, here is my code:
set serveroutput on size 10000;
declare
s_student_id grade.student_id%type;
g_score grade.score%type;
begin
for c in (select distinct grade.student_id, avg(grade.score) into s_student_id, g_score from grade inner join class on grade.class_id = class.class_id group by grade.student_id having count(class.course_id) > 4)
loop
dbms_output.put_line('Student' || c.student_id || ' :' || g_score);
end loop;
exception
when no_data_found then dbms_output.put_line('There are no students who selected more than 4 courses');
end;
/
Output:
anonymous block completed
Student1 :

I think this is what you're after:
set serveroutput on size 10000;
declare
v_counter integer := 0;
begin
for rec in (select grade.student_id,
avg(grade.score) g_score
from grade
inner join class on grade.class_id = class.class_id
group by grade.student_id
having count(class.course_id) > 4)
loop
v_counter := v_counter + 1;
dbms_output.put_line('Student: ' || rec.student_id || ', avg score: ' || rec.g_score);
end loop;
if v_counter = 0 then
raise no_data_found;
end if;
exception
when no_data_found then
dbms_output.put_line('There are no students who selected more than 4 courses');
end;
/
There are several points to note:
Good formatting of your sql statements (and pl/sql) will aid you when it comes to understanding, debugging and maintaining your code. If you can read it easily, chances are you'll understand it more quickly.
If you're using a cursor-for-loop, you don't need the into clause - that's only for when you are using an explicit select statement. You also don't need to declare your own variables to hold the data returned by the cursor - the cursor-for-loop declares the record variable to return the row into for you - in your example, that would be c, which I've renamed to rec for clarity.
Giving identifiers names that reflect what they are/do is also essential for ease of maintenance, readability etc.
When referring to the contents of the field from the cursor, use the record variable, e.g. rec.student_id, rec.g_score. Thus, it is important to give your columns aliases if you're doing anything other than a straight select (e.g. I've given avg(grade.score) an alias, but I didn't need to bother for grade.student_id)
If there are no records returned by the cursor, you will never get a no_data_found exception. Instead, you'll have to check to see if you had any rows returned - the easiest way to do this is to have some sort of counter. Once the loop has completed, you can then check the counter. If it shows that no rows were returned, you can then raise the no_data_found error yourself - or, more simply, you could skip the exception block and just put the dbms_output statement there instead. YMMV.
If you are going to go with the exception block, in production code you would most likely want to raise an actual error. In that case you would use RAISE or, if you need to pass a user defined error message out, RAISE_APPLICATION_ERROR.
Finally, I'm guessing this is some sort of homework question, and as such, the presence of the dbms_output statements is ok. However, out in the real world, you only ever want to use dbms_output for ad-hoc debugging or in non-production code because relying on dbms_output to pass information around to calling code is just asking for trouble. It's not robust, and there are far better, reliable methods of passing data around.

Related

PL/SQL: Run package after checking all rows in trigger

I have an inventory table with expected quantities and actually received quantities. Let's say inv.q_ex and inv.q_rd.
The INSERT to the table has a positive value in q_ex and a zero in q_rd because it hasn't arrived yet. I'd like to run a package when I detect that the q_rd value changes from 0 to something else, indicating it's been received and stored.
Making a trigger to detect after update and checking each row is easy, but I'm not sure how to ensure it only runs once.
The skeleton is:
CREATE OR REPLACE TRIGGER example
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
pkg.proc();
END IF;
END;
/
The problem I see is I only want it to run one time. I just need to identify when it needs to be executed. Ideally, on the first row where my condition is met, I would exit the loop (seems like a waste to keep checking when I already know I need to execute) and call my procedure.
I couldn't find a way to "exit" the for each and treat it as a normal AFTER UPDATE, so then I tried using both BEFORE UPDATE and AFTER UPDATE. The BEFORE portion would check each row and update a boolean. The AFTER portion would wait for that to happen and if it was true, call the procedure.
CREATE OR REPLACE TRIGGER example
BEFORE UPDATE ON inv
FOR EACH ROW
DECLARE
shouldExecute BOOLEAN;
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
shouldExecute := TRUE;
END IF;
END;
AFTER UPDATE ON inv
BEGIN
IF shouldExecute THEN
pkg.proc();
END IF;
END;
/
I suspect this wouldn't work anyway because, according to the syntax, it redeclares the boolean variable on each row. I thought that maybe I could make it "global" but regardless, turns out I can't add both BEFORE and AFTER to the same trigger for some reason (unless I didn't research enough), so I broke it out into two triggers. The problem now is I can't share that boolean between the two triggers. Can I share the value, or am I going about this all wrong?
There's a lot of little questions here so I'll try to answer them all :)
Regarding "FOR EACH ROW", Oracle triggers support two different triggering methods, STATEMENT or ROW. If you include the "FOR EACH ROW" in the definition, you'll get a row trigger, which is fired once per row affected by the query, which is what you seem to want here. Statement level triggers get fired only once for each query. An advantage to using row triggers is that you can use the :OLD and :NEW metavariables which refer to the previous and current row values.
As you've discovered, you can't add BEFORE and AFTER to the same trigger - you'll need to break them out into two triggers.
Unfortunately there isn't a simple way of sharing the boolean variable between the two triggers. The easiest way is probably to create a package with a public variable, which you can set in the BEFORE trigger, and check in the AFTER trigger.
The package would look something like this:
CREATE OR REPLACE PACKAGE PCKG_DEMO
AS
shouldExecute BOOLEAN;
END;
/
Then your BEFORE UPDATE trigger:
CREATE OR REPLACE TRIGGER beforeexample
BEFORE UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
PCKG_DEMO.shouldExecute := TRUE;
END IF;
END;
/
And your AFTER UPDATE trigger:
CREATE OR REPLACE TRIGGER afterexample
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF PCKG_DEMO.shouldExecute THEN
pkg.proc();
END IF;
END;
/
Those are a bit pseudocode-y - I don't have access to an Oracle database right now. You can read more about triggers here. Hope this helps!
You need to do more research. The link above takes you to trigger discussion for Oracle 9. I hope you are not actually using that version; support ended in 2007. Since version 11g Oracle has provided "Compound Triggers" where you can fire the same trigger both Before and After for both statement and row level. Compound triggers do allow sharing variables between the different invocations.

Dynamically checking if value is in sql query

I have a dynamically generated plsql region, in it, I have a bunch of dynamically generated items, some are popup_from_query and those have a query stored in an underlying table. But they also enable you to just type the return value in. So now I need to somehow check if the value you entered is in the LOV.
So I added some code to my validation(some also have extra conditions so I check those). I made a function that returns the relevant LOV, but reworked to something like this:
SELECT id --this is always the name of the column containing return value
FROM ( select something id, --this is the return value
something_else name --display value
from table
WHERE conditions)
WHERE id= :param;
This selects the return column from the lov, where the value is equal to the value we have entered, so this returns the entered value if it is in the LOV, and nothing if its not a valid value.
IF checking_lov THEN
a_lov := function_that_returns_an_sql_query;
DECLARE
x VARCHAR2(100);
BEGIN
EXECUTE IMMEDIATE
a_lov
INTO x
USING apex_application.g_f02(i); --value of parameter
EXCEPTION
WHEN no_data_found THEN
x := NULL;
END;
IF x IS NULL THEN
display_error; --I do stuff here to display error, not important to you
END IF;
END IF;
But I can't seem to get this to work. APEX doesn't find anything wrong with the code, it compiles without errors. But when I try it out, it throws an 'Invalid character' error, and the validation passes. I have tried perhaps making the entire a_lov query, adding an 'INTO x' and changing ':param' into 'apex_application.g_f02(i). But it just doesn't work.
Any ideas would be appreciated
Just wanted to post an update, actually got a solution going. And it was the stupidest thing ever.
The bit that is executed dynamically, I ended it with a semicolon, shouldnt have done that. That was all there was to it.
That one semicolon was what was messing me up, now it works. So if anyone stumbles upon this question, check your semicolons.

difference between cursor and select in a loop

I have been having this question for while now
we can Implement a cursor for Example
SET serveroutput ON;
DECLARE
CURSOR test_cursor
IS
SELECT * FROM employees;
BEGIN
FOR i IN test_cursor
LOOP
DBMS_OUTPUT.PUT_LINE(i.employee_id||' '||i.First_name);
END LOOP;
END;
Also we can implement the same in below way
SET serveroutput ON;
BEGIN
FOR rec IN
(SELECT * FROM employees
)
LOOP
DBMS_OUTPUT.PUT_LINE(rec.employee_id ||' ' ||rec.First_name);
END LOOP;
END;
Why do we need a cursor here then? Please could you let me know the differences and its advantages/disadvantages?
In both cases, you actually use cursors.
The first one is declared and named (explicit).
The second one is anonymous (implicit).
http://docs.oracle.com/cd/B10501_01/appdev.920/a96624/01_oview.htm#740.
I usually use explicit cursors for better code readability and in cases, when I want to reuse the cursor, I can fetch the data from result cache or fetch the data into variable and iterate through an array.
Also, as far as I know, execution plan is not generated as often as while using the implicit cursor (the DB searches for the query in SGA by query's hash and can find already stored execution plan, thus skips the execution plan generation).
https://docs.oracle.com/database/121/TGSQL/tgsql_sqlproc.htm#TGSQL175
So firstly, these are both known as cursor FOR LOOPs.
http://docs.oracle.com/database/121/LNPLS/cursor_for_loop_statement.htm#LNPLS1143
One usability difference between the two forms is that the second form places the SQL that is executing directly before the code in which the result set is used. This can make it easier to understand the code.
One useful syntax difference is that in the first form you can pass parameters into the cursor to modify its behaviour (see above link for syntax). So if you use the same basic cursor definition multiple times, but with different parameters to pass in, then use the former.

Use a declared variable in a SELECT statement

I'm using Oracle 10g and need to use a variable in a where clause of a SELECT; eg.
DECLARE
v_blah NUMBER;
BEGIN
v_blah := 13;
SELECT * FROM PEOPLE p WHERE p.LuckyNumber = v_blah;
END;
but am getting an error saying
PLS-00428: an INTO clause is expected in this SELECT statement
It seems to work fine in a DELETE or INSERT statement, so I'm not sure why it wouldn't work here.
The correct syntax is:
DECLARE
v_blah NUMBER := 13;
v_people_rec PEOPLE%ROWTYPE;
BEGIN
SELECT * INTO v_people_rec FROM PEOPLE p WHERE p.LuckyNumber = v_blah;
END;
The select statement in PL/SQL requires a place where store the query result. In this example the place is v_people_rec variable.
The example above expects exactly one row to be returned. In other cases it will throw exceptions NO_DATA_FOUND or TOO_MANY_ROWS.
That isn't anything to do with your parameter, it is because you're executing your code as a procedural block of code so it doesn't allow you to select to nothing.
What do you want to do with the result of the query? Display it to the screen? If so, select it to a cursor, iterate through and use dbms_output.

Can I delete rows using user defined functions in oracle?

I created a user defined function to delete some data. It doesn't work with delete but works with select. I am Oracle 9i.
The function is something like this:
create or replace function UFN_PURGEDATA(INPUTID IN VarChar2) return number is
Result number;
begin
Result := 0;
DELETE FROM MyTable WHERE MyTable.ID=INPID;
COMMIT;
Result := 1;
EXCEPTION WHEN OTHERS THEN
return(Result);
end UFN_PURGEDATA;
Then I use select UFN_PURGEDATA('test') from dual to run it but got result 0.
The answer to your question is "no".
If you remove your error "handling" you will find that the delete is failing with an exception like:
ORA-14551: cannot perform a DML
operation inside a query
i.e. you cannot perform an insert, update or delete from within a function called in a SELECT statement.
To execute this function in an IDE or SQL Plus, wrap it in some more PL/SQL like this:
declare
l_result number;
begin
l_result := my_function(123);
end;
However, you will need to add a RETURN statement to your function first otherwise it will fail.
(NB I said "handling" above in quotes because it is really "mishandling" - it completely disguises the actual problem in a very unhelpful way.)
You can perform DML inside a function used in a SELECT if you add PRAGMA AUTONOMOUS_TRANSACTION. For example:
create or replace function UFN_PURGEDATA(INPUTID IN VarChar2) return number is
pragma autonomous_transaction;
begin
DELETE FROM MyTable WHERE MyTable.ID=INPUTID;
COMMIT;
return 1;
EXCEPTION WHEN OTHERS THEN
return 0;
end UFN_PURGEDATA;
/
But you definitely want to avoid this approach if possible. In general, there's no way to know how many times a function will be executed if it's used in a SELECT.
Yes you can delete rows using user-defined functions in Oracle, but not from within a SELECT statement.
There are a couple of problems with your code:
- you don't return a value if your function does not raise an exception
- you must not use a function performing DML in a SELECT statement; if you remove your exception block, you get an ORA-14551
Why not create a procedure (instead of function) with OUT parameter returning the number? Without doing the autonomous transaction trick, Oracle doesn't want you running functions (used in selects) with "side-effects" (understandable why we don't want a select to result in DML changes).

Resources