CREATE TABLE COPPER_TAN_META
(
ID decimal(22,0) PRIMARY KEY,
NOTES clob,
ERROR varchar2(2000)
);
CREATE UNIQUE INDEX SYS_C0070016 ON COPPER_TAN_META(ID);
Before update
ID ERROR NOTES
20 <null> 27-APR-21 08.48.18 AM - XML is not a full-text article;
Update
update COPPER_TAN_META set error = 'trigger warning' where id = 20;
With trigger, I'd like to see this:
ID ERROR NOTES
20 trigger warning 27-APR-21 08.48.18 AM - XML is not a full-text article; <timestamp> - trigger warning;
My trigger doesn't work:
CREATE OR REPLACE TRIGGER copper_error_appends_to_note
AFTER UPDATE of error on COPPER_TAN_META
for each row
begin
IF :new.error is not null THEN
:new.notes := :old.notes || localtimestamp(0) || ' - ' || :new.error || '; ';
END IF;
end;
/
Error: ORA-04098: trigger 'B026.COPPER_ERROR_APPENDS_TO_NOTE' is invalid and failed re-validation
This works fine using a normal update statement, like
update copper_tan_meta
set notes = notes || localtimestamp(0) || ' - ' || :fileError || '; '
where id = :tanMetaId
I'm running this on SQuirreL SQL Client Version 3.5.3
new values cannot be changed for AFTER trigger type, but possible for BEFORE
localtimestamp without argument might be used within the direct concatenation, but localtimestamp with precision argument such as localtimestamp(0) could be used within a SQL statement.
So, rewrite the code block as
CREATE OR REPLACE TRIGGER copper_error_appends_to_note
BEFORE UPDATE OF ERROR ON copper_tan_meta
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT localtimestamp(0) INTO ts FROM dual;
IF :new.error IS NOT NULL THEN
:new.notes := :old.notes || ts || ' - ' || :new.error || '; ';
END IF;
END;
/
Related
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).
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);
I'm trying to turn pl/sql trigger that calculates the total of some cells in the table when the tale is changed. This is the code:
ALTER session SET nls_date_format='dd/mm/yyyy';
CREATE OR REPLACE TRIGGER TOTAL
AFTER UPDATE OR INSERT ON ORDER_ITEMS
FOR EACH ROW
DECLARE
temp NUMBER;
today DATE;
BEGIN
temp:=(:NEW.item_price-:NEW.discount_amount)*:NEW.quantity;
today := CURRENT_DATE;
:NEW.TOTAL := temp;
dbms_output.put_line('Updated on:' ||today || ' item number: ' ||:NEW.item_id|| 'order number:' ||:NEW.order_id|| 'total: ' ||:NEW.total);
END;
/
show errors
insert into order_items (ITEM_ID, ORDER_ID, PRODUCT_ID, ITEM_PRICE, discount_amount, QUANTITY)
VALUES (13, 7, 3, 553, 209, 2);
And I get this error:
00000 - "cannot change NEW values for this trigger type"
*Cause: New trigger variables can only be changed in before row
insert or update triggers.
*Action: Change the trigger type or remove the variable reference. No Errors. 1 rows inserted Updated on:06/01/2016 item number: 13order
number:7total:
I understand that the problem is updating a table during the trigger execution caused by an update to the same table.
As requested in comments I'm making my comment as an answer.
Your problem is because you are trying to change a value AFTER the value was persisted, try changing your trigger to BEFORE as:
CREATE OR REPLACE TRIGGER TOTAL
BEFORE UPDATE OR INSERT ON ORDER_ITEMS
FOR EACH ROW
DECLARE
temp NUMBER;
today DATE;
BEGIN
temp:=(:NEW.item_price-:NEW.discount_amount)*:NEW.quantity;
today := CURRENT_DATE;
:NEW.TOTAL := temp;
dbms_output.put_line('Updated on:' || today || ' item number: '
|| :NEW.item_id || 'order number:' || :NEW.order_id
|| 'total: ' ||:NEW.total);
END;
/
I'm trying to create a table within PL/SQL
how I can achieve that?
keep getting
Error report:
ORA-00933: "SQL command not properly ended"
here is the code that I have error with
DECLARE
station_id_ms1 NUMBER :=10347;
realtime_start DATE :=to_date('2012-01-01 00:00:00','YYYY-DD-MM HH24:MI:SS');
realtime_end DATE :=to_date('2012-07-01 00:00:00','YYYY-DD-MM HH24:MI:SS');
BEGIN
EXECUTE IMMEDIATE ('
CREATE TABLE new_table_name
AS
SELECT
((realtime - to_date(''01-JAN-1970'',''DD-MON-YYYY'')) * (86400)) AS realtime_ms1,
magnetic_ms_id,
ADC_value_pp_2_mgntc_fld_amp(ch2_value,ch2_gain_value,magnetic_ms_id,2) AS B_x_ms1,
ADC_value_pp_2_mgntc_fld_amp(ch1_value,ch1_gain_value,magnetic_ms_id,1) AS B_y_ms1,
real_nanosecs2*4/3*360/20e6 AS phase_x_ms1,
real_nanosecs1*4/3*360/20e6 AS phase_y_ms1
FROM
raw_mag
WHERE
magnetic_ms_id = '||station_id_ms1||'
AND realtime > '||realtime_start||'
AND realtime < '||realtime_end||'
AND ch1_tune_value = 0
AND realtime < pkg_timezone.change_timezone(gettime,''CET'',''UTC'')
');
END;
You should do the char-to-date conversion within the plsql-string that you excecute immediate.
The date you declared will be "back-cast" to a varchar2 in the concatenation and "re-cast" into a date again for the execution of the create table statement. And "all sorts of things" can happen in these two casts, so you want to make sure you're in control how the character-string is interpreted when cast to a date.
DECLARE
station_id_ms1 NUMBER :=10347;
realtime_start VARCHAR2(100) :='2012-01-01 00:00:00';
realtime_end VARCHAR2(100) :='2012-07-01 00:00:00';
BEGIN
EXECUTE IMMEDIATE ('
CREATE TABLE new_table_name
AS
SELECT
((realtime - to_date(''01-JAN-1970'',''DD-MON-YYYY'')) * (86400)) AS realtime_ms1,
magnetic_ms_id,
ADC_value_pp_2_mgntc_fld_amp(ch2_value,ch2_gain_value,magnetic_ms_id,2) AS B_x_ms1,
ADC_value_pp_2_mgntc_fld_amp(ch1_value,ch1_gain_value,magnetic_ms_id,1) AS B_y_ms1,
real_nanosecs2*4/3*360/20e6 AS phase_x_ms1,
real_nanosecs1*4/3*360/20e6 AS phase_y_ms1
FROM
raw_mag
WHERE
magnetic_ms_id = '||station_id_ms1||'
AND realtime > to_date(''' || realtime_start || ''', ''YYYY-DD-MM HH24:MI:SS'')
AND realtime < to_date(''' || realtime_end || ''', ''YYYY-DD-MM HH24:MI:SS'')
AND ch1_tune_value = 0
AND realtime < pkg_timezone.change_timezone(gettime,''CET'',''UTC'')
');
END;
I would use binds for station_id_ms1, realtime_start, realtime_end:
EXECUTE IMMEDIATE '
...
WHERE
magnetic_ms_id = :station_id_ms1
AND realtime > :realtime_start
AND realtime < :realtime_end
...
' USING IN station_id_ms1, realtime_start, realtime_end
I have a supplier_product table ( supp_id, prod_id, invoice_id,price) and an invoice table (invoice_id, balance). I tried a stored proc. given (supp_id) it should all the existing invoice_id and display the balance. here's my code:
set serverouput on;
create or replace
Procedure SUP_loop
(v_SUPPLIER_ID int )
AS
CURSOR c_SUP IS
select SUPPLIER_ID , SUPP_INVOICE_ID, balance
from SUPPLIER_PRODUCT, supplier_invoice
where SUPPLIER_ID=v_SUPPLIER_ID
and supp_invoice_id.supplier_product=supp_invoice_id.supplier_invoice;
BEGIN
--LOOP WITH IMPLICIT VARIABLE DECLARED
--AUTOMATIC, OPEN FETCH, CLOSE
FOR v_SUP_data IN c_SUP LOOP
DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_SUP_data.SUPPLIER_ID) || ' ' ||
TO_CHAR(v_SUP_data.SUPP_INVOICE_ID) || ' ' ||
TO_CHAR(v_SUP_data.balance) );
END LOOP;
END;
/
the error i am getting is v_sup_data Error(20,31): PLS-00364: loop index variable 'V_SUP_DATA' use is invalid
Error(9,74): PL/SQL: ORA-00904: "SUPP_INVOICE_ID"."SUPPLIER_INVOICE": invalid identifier
You have the field and the table names swapped round the wrong way.
You have...
supp_invoice_id.supplier_invoice
...where you should have...
supplier_invoice.supp_invoice_id
:D
The syntax for referring to a column is <>.<>. So your cursor query needs the join condition to be supplier_produce.supp_invoice_id = supplier_invoice.supp_invoice_id, i.e.
create or replace
Procedure SUP_loop
(v_SUPPLIER_ID int )
AS
CURSOR c_SUP IS
select SUPPLIER_ID , SUPP_INVOICE_ID, balance
from SUPPLIER_PRODUCT, supplier_invoice
where SUPPLIER_ID=v_SUPPLIER_ID
and supplier_product.supp_invoice_id = supplier_invoice.supp_invoice_id;
BEGIN
--LOOP WITH IMPLICIT VARIABLE DECLARED
--AUTOMATIC, OPEN FETCH, CLOSE
FOR v_SUP_data IN c_SUP LOOP
DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_SUP_data.SUPPLIER_ID) || ' ' ||
TO_CHAR(v_SUP_data.SUPP_INVOICE_ID) || ' ' ||
TO_CHAR(v_SUP_data.balance) );
END LOOP;
END;
/