I am having difficulty figuring out how to implement a before insert trigger using a cursor that will compare the insert number with the max value of the 1 column table (NUM), then print out the corresponding message. Any help would be greatly appreciated.
create or replace trigger MAXSOME
before insert on SOMENUMBERS for each row
declare
cursor pointer is
select max(NUM) from SOMENUMBERS;
x number;
begin
x := :new.NUM;
if x > pointer.num then
dbms_output.put_line('The new number ' || x || ' is greater than the greatest number in the table.');
elsif x := pointer then
dbms_output.put_line('The new number ' || x || ' is the same as the greatest number in the table.');
else
dbms_output.put_line(pointer.num || ' is still the largest number in the table.');
end if;
end;
/
If you declare a cursor you have to open it in a loop but you don't even need a cursor. Using dbms_output is not going to work very well as one session may insert and you may be waiting for the output using another session.. Create another table and log the output there.
I see Knuckles suggests an autonomous transaction but my test case succeeded without one.
create or replace trigger MAXSOME
before insert on SOMENUMBERS
for each row
declare
x SOMENUMBERS.NUM%TYPE;
begin
select max(NUM) into x from SOMENUMBERS;
if x > :new.NUM then
dbms_output.put_line('The new number ' || :new.NUM || ' is greater than the greatest number in the table.');
elsif x = :new.NUM then
dbms_output.put_line('The new number ' || :new.NUM || ' is the same as the greatest number in the table.');
else
dbms_output.put_line(:new.NUM || ' is still the largest number in the table.');
end if;
end;
Related
I'm trying to calculate the GPA within a function that accepts a studentID as an input. My issue is that the variable lv_gpa_calc isn't adding to itself when the loop works through the cursor set. I added the DBMS_OUTPUT.PUT_LINE to make sure that it's working through the cursor set correctly and that prints to the screen the correct individual row values of what lv_gpa_calc should be, but when it gets returned in the function in a SQL block, it isn't adding all of those values together. Can you not set a variable to itself within a CURSOR FOR LOOP?
Update: initializing the lv_gpa_calc fixed the problem where the variable value wasn't adding to itself.
CREATE OR REPLACE
FUNCTION CALCULATE_GPA
(p_studentID IN number)
RETURN NUMBER
IS
CURSOR cur_gpa IS
SELECT grade, grade_value, credit_hours
FROM grade
JOIN enrollment USING (Grade)
JOIN section USING (term_code, subject_code, course_number, section)
JOIN course USING (subject_code, course_number)
WHERE student_ID = p_studentID;
lv_gpa_calc NUMBER(4,2):=0;
BEGIN
FOR rec_gpa IN cur_gpa LOOP
lv_gpa_calc:= lv_gpa_calc + ((rec_gpa.grade_value * rec_gpa.credit_hours)/rec_gpa.credit_hours);
DBMS_OUTPUT.PUT_LINE(lv_gpa_calc);
END LOOP;
RETURN lv_gpa_calc;
END CALCULATE_GPA;
The problem in your code is that variable lv_gpa_calc is not initialized. Adding whatever to NULL will result as NULL.
Simplified working test case:
--DROP TABLE my_numbers;
CREATE TABLE my_numbers (
id NUMBER
);
/
BEGIN
FOR l_i IN 1..10 LOOP
INSERT INTO my_numbers VALUES (DBMS_RANDOM.RANDOM);
END LOOP;
COMMIT;
END;
/
SELECT * FROM my_numbers;
/
DECLARE
CURSOR cur IS
SELECT id
FROM my_numbers;
l_sum NUMBER(10) := 0;
BEGIN
FOR rec IN cur LOOP
l_sum := l_sum + rec.id;
DBMS_OUTPUT.PUT_LINE('l_sum = ' || l_sum);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Sum = ' || l_sum);
END;
/
Important line is:
l_sum NUMBER(10) := 0;
Without initialization :=0 it won't work.
I am writing a simple trigger that is supposed to just send a message with the updated Count of rows as well as the old value of Gender and the updated value of Gender. When i run an update however I am getting the error that the table is mutating and the table might not be able to see it but I'm not exactly sure why.
trigger
create or replace trigger updatePERSONS
after update
on PERSONS
for each row
declare
n int;
oldGender varchar(20):= :OLD.Gender;
newGender varchar(20):= :NEW.Gender;
begin
select Count(*)
into n
from PERSONS;
if (oldGender != newGender) then
dbms_output.put_line('There are now '|| n || ' rows after update. Old gender: ' || oldGender
|| ', new Gender: ' || newGender);
end if;
End;
`
i know it has to do with the select statement after begin but how else would i get count of rows?
As #San points out, a row-level trigger on persons cannot generally query the persons table.
You'd need two triggers, a row-level trigger that can see the old and new gender and a statement-level trigger that can do the count. You could also, if you're using 11g, create a compound trigger with both row- and statement-level blocks.
create or replace trigger trg_stmt
after update
on persons
declare
l_cnt integer;
begin
select count(*)
into l_cnt
from persons;
dbms_output.put_line( 'There are now ' || l_cnt || ' rows.' );
end;
create or replace trigger trg_row
after update
on persons
for each row
begin
if( :new.gender != :old.gender )
then
dbms_output.put_line( 'Old gender = ' || :old.gender || ', new gender = ' || :new.gender );
end if;
end;
Im trying to create a trigger that restricts the amount a reader can read in a given month.
CREATE OR REPLACE trigger Readings_Limit
Before update or insert on reading
for each row declare
readingcount integer;
max_read integer := 5;
Begin
Select count(*) into readingcount
from (select *
from Reading
where to_char(DateRead, 'YYYY-MM') = to_char(DateRead, 'YYYY-MM'))
where employeeid = :new.employeeid;
if :old.employeeid = :new.employeeid then
return;
else
if readingcount >= max_read then
raise_application_error (-20000, 'An Employee can only read 5 per month');
end if;
end if;
end;
This restricts the reader to 5 max in total no matter the month, i can't seem to get it to be 5 max each month. any ideas greatly appreciated!
Try to rewrite your trigger like this:
CREATE OR REPLACE trigger Readings_Limit
Before update or insert on reading
for each row
declare
readingcount integer;
max_read integer := 5;
Begin
Select count(*) into readingcount
from Reading
where DateRead between trunc(sysdate,'MM') and last_day(sysdate)
and employeeid = :new.employeeid;
if :old.employeeid = :new.employeeid then
return;
else
if readingcount >= max_read then
raise_application_error (-20000, 'An Employee can only read 5 per month');
end if;
end if;
end;
You add actual month into yout select and you avoid unnecessary date conversion.
I don't understand the condition
if :old.employeeid = :new.employeeid then
Does it mean, the trigger should not fire on updates? In this case it is better to make trigger only for insert or use clause if inserting then...
In order to properly create this validation using a trigger a procedure should be created to obtain user-specified locks so the validation can be correctly serialized in a multi-user environment.
PROCEDURE request_lock
(p_lockname IN VARCHAR2
,p_lockmode IN INTEGER DEFAULT dbms_lock.x_mode
,p_timeout IN INTEGER DEFAULT 60
,p_release_on_commit IN BOOLEAN DEFAULT TRUE
,p_expiration_secs IN INTEGER DEFAULT 600)
IS
-- dbms_lock.allocate_unique issues implicit commit, so place in its own
-- transaction so it does not affect the caller
PRAGMA AUTONOMOUS_TRANSACTION;
l_lockhandle VARCHAR2(128);
l_return NUMBER;
BEGIN
dbms_lock.allocate_unique
(lockname => p_lockname
,lockhandle => p_lockhandle
,expiration_secs => p_expiration_secs);
l_return := dbms_lock.request
(lockhandle => l_lockhandle
,lockmode => p_lockmode
,timeout => p_timeout
,release_on_commit => p_release_on_commit);
IF (l_return not in (0,4)) THEN
raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return);
END IF;
-- Must COMMIT an autonomous transaction
COMMIT;
END request_lock;
To have the least impact on scalability the serialization should be done at the finest level, which for this constraint is per employeeid and month. Types may be used in order to create variables to store this information for each row before the constraint is checked after the statement has completed. These types can be either defined in the database or (from Oracle 12c) in package specifications.
CREATE OR REPLACE TYPE reading_rec
AS OBJECT
(employeeid NUMBER(10) -- Note should match the datatype of reading.employeeid
,dateread DATE);
CREATE OR REPLACE TYPE readings_tbl
AS TABLE OF reading_rec;
The procedure and types can then be used in a compound trigger (assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions)
CREATE OR REPLACE TRIGGER too_many_readings
FOR INSERT OR UPDATE ON reading
COMPOUND TRIGGER
-- Table to hold identifiers of inserted/updated readings
g_readings readings_tbl;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal readings table
g_readings := readings_tbl();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the inserted/updated readings
IF ( INSERTING
OR :new.employeeid <> :old.employeeid
OR :new.dateread <> :old.dateread)
THEN
g_readings.EXTEND;
g_readings(g_readings.LAST) := reading_rec(:new.employeeid, :new.dateread);
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_readings
IS
SELECT DISTINCT
employeeid
,trunc(dateread,'MM') monthread
FROM TABLE(g_readings)
ORDER BY employeeid
,trunc(dateread,'MM');
CURSOR csr_constraint_violations
(p_employeeid reading.employeeid%TYPE
,p_monthread reading.dateread%TYPE)
IS
SELECT count(*) readings
FROM reading rdg
WHERE rdg.employeeid = p_employeeid
AND trunc(rdg.dateread, 'MM') = p_monthread
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated readings there exists more than
-- 5 readings for the same employee in the same month. Serialise the
-- constraint for each employeeid so concurrent transactions do not
-- affect each other
FOR r_reading IN csr_readings LOOP
request_lock('TOO_MANY_READINGS_'
|| r_reading.employeeid
|| '_' || to_char(r_reading.monthread, 'YYYYMM'));
OPEN csr_constraint_violations(r_reading.employeeid, r_reading.monthread);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Employee ' || r_reading.employeeid
|| ' now has ' || r_constraint_violation.readings
|| ' in ' || to_char(r_reading.monthread, 'FMMonth YYYY'));
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
You need to set the month you are looking at, so if you are considering the current month, make the inner query read like this:
( select * from Reading
where to_char(DateRead,'YYYY-MM') = to_char(DateRead,'YYYY-MM')
and to_char(sysdate,'YYYY-MM') = to_char(DateRead,'YYYY-MM'))
that way it will always compare to the current month and should move as your date moves.
i working since some days with APEX and i added a Shuttle also a text field which contains after processing the selected values.
for the shuttle i have made a LOV (contains stations)
example:
Be
Fd
Au
...
the target is get all object's which contains the selected station's
but if i run the select i get more objects - also the object which does not contain any selected station, but i don’t know why
in the SQL statement i have the following: (ds_bess001 is the field for stations)
AND INSTR (:P4_BES_EN, zl.ds_bes001) > 0
for the shuttle i use PL/SQL below
DECLARE
v_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
v_in_text VARCHAR2 (500) := '';
BEGIN
v_selected := APEX_UTIL.STRING_TO_TABLE (:P4_BES);
FOR i IN 1 .. v_selected.COUNT
LOOP
v_in_text := v_in_text || v_selected (i);
IF i < v_selected.COUNT
THEN
-- v_in_text := v_in_text || ', ';
v_in_text := v_in_text || ',';
END IF;
END LOOP;
-- v_in_text := '''' || replace(:P1_SHUTTLE, ':', ''',''') || '''';
:P4_BES_EN := v_in_text;
END;
could anybody help me please?!
now i found the Problem i get the variable example Ap','Fd','Ab and the Problem is thats the variable miss the ' at the beginning and at the end
could anybody tel me how i could add the missing ' in the variable?
try it with
:P4_BES_EN := ''''||v_in_text||'''';
for ' at the beginning and the end.
and i think you have to alter your instr to something like this
:P4_BES_EN := ','''||v_in_text||''',';
AND INSTR (:P4_BES_EN, ','||zl.ds_bes001||',') > 0
e.g.:
DECLARE
v_in_text VARCHAR2 (500) := 'Ap'',''Fd'',''Ab';
BEGIN
v_in_text := ','''||v_in_text||''',';
dbms_output.put_line(v_in_text);
END;
I have an Oracle table which contains data like 'Shiv------Shukla' (consider '-' as space).
Now I need to write a program which leaves just one space and removes all other spaces.
Here is the program which I've made but it is not giving me expected result.
DECLARE
MAX_LIMIT VARCHAR2(50):=NULL;
REQ VARCHAR2(20):=NULL;
CURSOR C1 IS
SELECT *
FROM ASSET_Y;
BEGIN
FOR REC IN C1
LOOP
MAX_LIMIT:=LENGTH(REC.NAME)-LENGTH(REPLACE(REC.NAME,'-'));
FOR I IN 1..MAX_LIMIT
LOOP
UPDATE ASSET_Y
SET NAME=REPLACE(REC.NAME,'--','-')
WHERE REC.SNO=ASSET_Y.SNO;
COMMIT;
SELECT ASSET_Y.NAME INTO REQ FROM ASSET_Y WHERE ASSET_Y.SNO=REC.SNO;
DBMS_OUTPUT.PUT_LINE(REQ);
END LOOP;
END LOOP;
COMMIT;
END;
/
My table is
SQL> select * from asset_y;
SNO NAME FL
---------- -------------------- --
1 Shiv------Shukla y
2 Jinesh y
after running the procedure i m getting the following output.
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
Shiv---Shukla
PL/SQL procedure successfully completed.
Since regexp_replace is not available in Oracle 9i maybe you can use owa_pattern routines for simple regex replaces:
owa_pattern.change(fStr, '\s+', ' ', 'g');
More info about owa_pattern package can be found here
Bear in mind, that "\s" will match tabs and newlines as well.
With Oracle 9 you could write your own function:
CREATE FUNCTION remove_multi_spaces( in_value IN VARCHAR2 )
RETURN VARCHAR2
AS
v_result VARCHAR2(32767);
BEGIN
IF( in_value IS NOT NULL ) THEN
FOR i IN 1 .. ( LENGTH(in_value) - 1 ) LOOP
IF( SUBSTR( in_value, i, 2 ) <> ' ' ) THEN
v_result := v_result || SUBSTR( in_value, i, 1 );
END IF;
END LOOP;
v_result := v_result || SUBSTR( in_value, -1 );
END IF;
RETURN v_result;
END;
and call it in a single update-statement:
UPDATE asset_y
SET name = replace_multi_spaces( name );
BTW: With Oracle 10 you could use REGEXP_REPLACE.
Your problem is this part:
SET NAME=REPLACE(REC.NAME,'--','-')
However many times you do that within the inner loop it starts with the same value of REC.NAME as before. Changing it to this would fix it:
SET NAME=REPLACE(NAME,'--','-')
However, it is a pretty inefficient way to do this job if the table is large. You could instead do this:
BEGIN
LOOP
UPDATE ASSET_Y
SET NAME=REPLACE(NAME,'--','-')
WHERE NAME LIKE '%--%';
EXIT WHEN SQL%ROWCOUNT = 0;
END LOOP;
END;
/
Another way:
CREATE OR REPLACE
FUNCTION remove_multi_spaces( in_value IN VARCHAR2 )
RETURN VARCHAR2 IS
v_result VARCHAR2(32767) := in_value;
BEGIN
LOOP
EXIT WHEN INSTR(v_result,' ') = 0;
v_result := REPLACE(v_result, ' ', ' ');
END LOOP;
RETURN v_result;
END remove_multi_spaces;
Ack loops! No need to loop this
This will work in T-SQL...unfortunately I have no pl/sql environment to write this in. PL/SQL will have equivlents to everything used here (I think substr instead of substring and | instead of +)
declare #name varchar(200)
set #name = 'firstword secondword'
select left(#name,(patindex('% %',#name)-1)) + ' ' + ltrim(substring(#name,(patindex('% %',#name)+1),len(#name)))
You'll have to retool it to work for oracle and you'll need to replace any reference to #name to asset_y.name
select left(asset_y.name,(patindex('% %',asset_y.name)-1)) || ' ' || ltrim(substring(asset_y.name,(patindex('% %',asset_y.name)+1),len(asset_y.name)))
Sorry if it won't run as is, as I mentioned I lack an oracle install here to confirm...
Just to add...I normally turn that query above into a function named formatname and call it as select formatname(array_y.name) from... This allows me to include some form of error handling. The query will fail if patindex('% %',array_v.name) returns a null...meaning there is no space. You could do the same in a select statement using cases I guess:
select case when patindex('% %',array_v.name) > 0 then
left(asset_y.name,(patindex('% %',asset_y.name)-1)) || ' ' || ltrim(substring(asset_y.name,(patindex('% %',asset_y.name)+1),len(asset_y.name)))
else asset_y.name
from...