I am new to Oracle (11gr2) and I have the following script:
BEGIN
DECLARE
source varchar2(1);
BEGIN
dbms_output.enable;
BEGIN
EXECUTE IMMEDIATE 'DROP VIEW SP_AD;';
SELECT SOURCE INTO source FROM map_switch WHERE ROWNUM = 1;
IF source = 'A'
THEN
EXECUTE IMMEDIATE 'DROP TABLE SP_AD_B;';
EXECUTE IMMEDIATE 'RENAME TABLE SP_AD_A TO SP_AD;';
ELSE
EXECUTE IMMEDIATE 'DROP TABLE SP_AD_A;';
EXECUTE IMMEDIATE 'RENAME TABLE SP_AD_B TO SP_AD;';
END IF;
COMMIT WORK;
dbms_output.put_line('SP_AD table issue fixed');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Exception, rolling back transaction, SP_AD not resolved.');
ROLLBACK WORK;
END;
END;
END;
/
Essentially, its determining which table to drop, then it drops the view and renames the other table.
If I run the statements individually, it works perfectly well, but in the script above, it returns procedure executed successfully but nothing was executed.
I'm suspecting that its rolling back for some odd reason, but I'm hesitating to execute it without the rollback in place (these tables have in excess of 300,000 records).
Can someone tell me what's wrong and also, is there something wrong with my exception block?
As pointed out by commenters, there are a few reasons why your code isn't working as expected.
Firstly, don't use semicolons inside the strings that you pass to EXECUTE IMMEDIATE, as doing that will give you an ORA-00911 'invalid character' error:
SQL> BEGIN
2 EXECUTE IMMEDIATE 'DROP TABLE SP_AD_B;';
3 END;
4 /
BEGIN
*
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at line 2
After running this, you can then verify that the table still exists:
SQL> SELECT * FROM SP_AD_B;
no rows selected
(I don't have your table SP_AD_B, so I just created one named SP_AD_B with a single integer column in it. I didn't bother putting any data in it.)
If you remove the semicolon inside the string, not the one outside, it works:
SQL> BEGIN
2 EXECUTE IMMEDIATE 'DROP TABLE SP_AD_B';
3 END;
4 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM SP_AD_B;
SELECT * FROM SP_AD_B
*
ERROR at line 1:
ORA-00942: table or view does not exist
Now that the table's gone, we get an error attempting to query it.
Hopefully, this should allow you to fix your script so that it works and drops the relevant tables.
But why weren't you getting any helpful information in your output message? Well, let's recreate the SP_AD_B table, and reintroduce the semicolon, and try dropping the table again, but with an EXCEPTION handler similar to yours:
SQL> BEGIN
2 EXECUTE IMMEDIATE 'DROP TABLE SP_AD_B;';
3 EXCEPTION
4 WHEN OTHERS THEN
5 dbms_output.put_line('Exception, rolling back transaction, SP_AD not resolved.');
6 END;
7 /
Exception, rolling back transaction, SP_AD not resolved.
PL/SQL procedure successfully completed.
In this case, we got an error message telling us something went wrong, so the table wasn't dropped. But what went wrong? There are thousands of errors that Oracle can report, and it can be difficult to guess what the problem is without knowing the error message.
There are a number of approaches you can take here. Firstly, you could write the error message, in SQLERRM to dbms_output:
SQL> BEGIN
2 EXECUTE IMMEDIATE 'DROP TABLE SP_AD_B;';
3 EXCEPTION
4 WHEN OTHERS THEN
5 dbms_output.put_line('Exception, rolling back transaction, SP_AD not resolved.');
6 dbms_output.put_line('Error message was: ' || SQLERRM);
7 END;
8 /
Exception, rolling back transaction, SP_AD not resolved.
Error message was: ORA-00911: invalid character
PL/SQL procedure successfully completed.
You can also use dbms_utility.format_error_backtrace to return the current stacktrace as a string, if you so wish. That might help you figure out where the error came from.
Alternatively, you can reraise the exception. Using RAISE on its own in an EXCEPTION handler reraises the current exception:
SQL> BEGIN
2 EXECUTE IMMEDIATE 'DROP TABLE SP_AD_B;';
3 EXCEPTION
4 WHEN OTHERS THEN
5 dbms_output.put_line('Exception, rolling back transaction, SP_AD not resolved.');
6 RAISE;
7 END;
8 /
Exception, rolling back transaction, SP_AD not resolved.
BEGIN
*
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at line 6
However, given the fact that your EXCEPTION handler isn't really doing anything helpful, the best approach is quite probably to get rid of it altogether.
Your exception handler doesn't achieve anything because you can't commit or rollback DDL statements such as CREATE, ALTER, DROP or TRUNCATE. Each of these statements issues a COMMIT immediately before and after it runs. If a DROP succeeds but a RENAME fails, you can't get the dropped table back by rolling back a transaction. I'd recommend getting rid of your COMMIT WORK and ROLLBACK WORK statements.
Finally, commenter Jeffrey Kemp noticed this line:
SELECT SOURCE INTO source FROM map_switch WHERE ROWNUM = 1;
This assigns into a variable named source the value of the column SOURCE from some arbitrary row of the table map_switch. It could be any row; as you haven't specified any ordering, Oracle is free to order the rows of map_switch however it likes.
If there's only one row in the table, then it's clear which row you'll get back. However, if this is the case, why specify ROWNUM = 1? Does the table have more than one row and is the ROWNUM = 1 part is just there to silence an 'exact fetch returns more than requested number of rows' error?
You would be better off doing something like the following:
SELECT SOURCE INTO source
FROM (SELECT SOURCE FROM map_switch ORDER BY some_column)
WHERE ROWNUM = 1;
I don't know what columns there are in your map_switch table, so I've just used some_column above as a placeholder for one of them. Choose a column that has unique values, if possible.
Note that we can't simply do SELECT ... WHERE ROWNUM = 1 ORDER BY some_column as that would apply the ROWNUM = 1 clause before doing the sorting, and there's not a lot of point sorting a single row as there's only one order it can be returned in.
Related
I just started working with PL/SQL. The database is for a game I want to integrate into my discord bot. Both the DB and the bot are running on Oracle Cloud.
The DB has one table, players, consisting of a discord user id, they have a level initiated with 1, exp initiated with 0 and mana initiated with 100. For now, the first thing I wanted to implement was a TRIGGER that would activate when exp is updated on table players, check if exp reached the level up threshold and if so, reduce exp to 0 and increase the level by 1.
Right now, I have this:
CREATE OR REPLACE trigger level_up
BEFORE UPDATE
ON PLAYERS
FOR EACH ROW
WHEN (:new.EXP >= PLAYERS.LEVEL * 100)
begin
:new.EXP := 0;
:new.LEVEL := :old.LEVEL + 1;
end;
When I try to run this, I get the following error:
"Error: ORA-00904: : invalid identifier"
Nothing is highlighted and in SQL Developer, when I right-click it and then click "Go to source", it doesn't highlight anything and just throws the cursor to the beginning of the worksheet.
I have already tried a couple different things like
BEFORE UPDATE OF EXP ON PLAYERS
with the rest more or less the same and even tried working with AFTER UPDATE:
CREATE OR REPLACE trigger level_up
AFTER UPDATE
ON PLAYERS
FOR EACH ROW
begin
UPDATE players
SET players.exp = 0,
players.level = players.level + 1
WHERE players.exp < players.level * 100
end;
This gave me multiple errors though:
Error(6,9): PL/SQL: SQL Statement ignored
Error(10,5): PL/SQL: ORA-00933: SQL command not properly ended
Error(10,8): PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following: ( begin case declare end exception exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge json_exists json_value json_query json_object json_array
At this point I am fully prepared to just abandon the oracle db and switch to mongodb or something, it's just bugging me out that I can't figure out what I am doing wrong.
Thank you for your time!
If the 1st trigger code is OK to you, then just fix what's wrong - why abandoning the whole idea of using Oracle? It's not its (Oracle's fault) you don't know how to use it.
Here's an example.
Table contains just two columns, necessary for this situation.
Column name can't be just level - that's name of a pseudocolumn in hierarchical query and is reserved word for that purpose; I renamed it to c_level (well, you can name it level, but it should then be enclosed into double quotes and you'd have to reference it that way - with double quotes - every time you access it, using exactly the same letter case. In Oracle, that's generally a bad idea).
SQL> create table players
2 (exp number,
3 c_level number
4 );
Table created.
Trigger: you should have removed colon when referencing the new pseudorecord in the when clause (while in trigger body you have to use it, the colon sign). Also, you don't reference c_level with table's name (players.c_level) but also using the pseudorecord.
SQL> create or replace trigger level_up
2 before update on players
3 for each row
4 when (new.exp >= new.c_level * 100)
5 begin
6 :new.exp := 0;
7 :new.c_level := :old.c_level + 1;
8 end;
9 /
Trigger created.
Let's try it. Initial row:
SQL> insert into players (exp, c_level) values (50, 1);
1 row created.
SQL> select * from players;
EXP C_LEVEL
---------- ----------
50 1
Let's update exp so that its value forces trigger to fire:
SQL> update players set exp = 101;
1 row updated.
New table contents:
SQL> select * from players;
EXP C_LEVEL
---------- ----------
0 2
SQL>
If that was your intention, then it kind of works.
The assignment I am trying to do is
"Create a procedure that places the names of all presidents who were born in one specific
state, in a temporary table. Display the contents of this table."
The procedure complies but when I try to invoke it, it gives me:
00000 - "table or view does not exist"
Error(8,5): PLS-00103: Encountered the symbol "CREATE" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior
I have been stuck for a while now. Does anybody know what I am doing wrong?
My code so far is:
CREATE OR REPLACE PROCEDURE stateofpresident(p_state president.state_born%TYPE)
AS
CURSOR c_state IS
SELECT *
FROM president;
BEGIN
execute immediate 'CREATE TABLE presidentFromState;
(
president_name VARCHAR2
)';
FOR r_state IN c_state LOOP
IF(p_state = r_state.state_born) THEN
execute immediate 'INSERT INTO presidentFromState VALUES(r_state.pres_name)';
commit;
END IF;
END LOOP;
execute immediate 'DROP TABLE presidentFromState';
END stateofpresident;
/
SET SERVEROUT ON
BEGIN
stateofpresident('VIRGINIA');
END;
/
SELECT *
FROM presidentFromState;
The immediate cause of your error is the semi-colon (;) at "presidentFromState;" At run fhat terminates the statement and the SQL interpreter at that point does not know what is want, the create syntax is invalid. The statement compiles because at compile time it is a properly formatted string. That is why dynamic SQL should be avoid if at all possible. Your script also has an additional error. Your last select will fail as the table presidentFromState ws not only created but also dropped in the procedure. Finally, just an FYI, the entire FOR cursor and the cursor itself is entirely unnecessary, the entire operation can be completed is one statement: Look into the structure
Insert into table_name(columns)
Select columns ...
Since this obviously an assignment or tutorial I'll leave the exact for your research.
I am trying to bulk insert rows from one table to another. I want to skip any errors that occur while doing this but log them as well. I used the following PL/SQL command to create an error log table for the table I am trying to insert all the values into:
BEGIN
DBMS_ERRLOG.create_error_log (
dml_table_name => 'ROBOT_ID_TOOL_DUMP_IDPK.TEST'
, skip_unsupported => TRUE);
END;
I then do a simple insert with logging turned on:
INSERT INTO table2
SELECT * FROM table1
LOG ERRORS INTO ERR$_table2;
The logging works but my insert is stopping, and rolling back after the first exception is hit (PK or unique constraint for example). I don't want to rollback when an exception is encountered though, I want that row to be skipped, and to continue trying to insert all remaining rows (but logging the problem row). I thought that is what the skip_unsupported parameter was doing. I have tried setting this value to FALSE and am still encountering the same issue.
I missed a step on my insert.
INSERT INTO table2
SELECT * FROM table1
LOG ERRORS INTO ERR$_table2 REJECT LIMIT UNLIMITED;
The REJECT LIMIT UNLIMITED flag Continues through every error.
This question already has answers here:
How to handle exception and execute remaining statements?
(2 answers)
Closed 8 years ago.
In the below code first insert statement is,
insert into customer_master select max(customerid)+1,customer_name from customer_master
where customer_name not in (select customername from customer_master);
Am getting null value insert error for this. Yes, that is right.
But it is stopping below statements of the block from executing.
insert into customer_account_mapping select customerid,upper(pcd(i)),upper(acd(i)),cost from customer_master where customername=customer_name and concat(upper(pcd(i)),upper(acd(i))) not in (select concat(upper(pcode),upper(acode)) from customer_account_mapping);
insert into user_permissions select distinct user_id,sales_person_name,sales_mgr_name,upper(pcd(i)),upper(acd(i)) from user_permissions where sales_person_name=sales_person and concat(upper(pcd(i)),upper(acd(i))) not in (select concat(upper(pcode),upper(acode)) from user_permissions) and rownum<2 ;
Complete code is given below.
create or replace
procedure dashboard_addtion
(customer_name varchar2,pcd parray,acd aarray,sales_person varchar2,cost number)
IS
begin
insert into customer_master select max(customerid)+1,customer_name from customer_master
where customer_name not in (select customername from customer_master);
for i in 1..acd.count loop
insert into customer_account_mapping select customerid,upper(pcd(i)),upper(acd(i)),cost from customer_master where customername=customer_name and concat(upper(pcd(i)),upper(acd(i))) not in (select concat(upper(pcode),upper(acode)) from customer_account_mapping);
insert into user_permissions select distinct user_id,sales_person_name,sales_mgr_name,upper(pcd(i)),upper(acd(i)) from user_permissions where sales_person_name=sales_person and concat(upper(pcd(i)),upper(acd(i))) not in (select concat(upper(pcode),upper(acode)) from user_permissions) and rownum<2 ;
commit;
end loop;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
end;
Am getting null value insert error for this. Yes, that is right.
But it is stopping below statements of the block from executing.
The exception caused process flow to go to the EXCEPTION handler section. That is the end of the program block. So processing stops after handling the exception. This is standard. So in fact, your have titled your question wrongly, because this is the proper way to handle exceptions. The whole point is to preserve the ACIDity of the transaction. Why would you want to continue processing if the first part of your program fails?
Although, having described this as the proper way I feel I must say that "handling" exceptions by simply using DBMS_OUTPUT is extremely bad practice. That won't work in production code because calling progarm won't know an exception occurred. Your procedure needs to log the error and then re-raise the exception. Anything else is just asking for trouble.
I am a newbie in PLSQL and I would like to create a trigger that checks first if there is a record in a table before making an update.
The code I got so far is:
CREATE OR REPLACE TRIGGER table_bu
BEFORE UPDATE ON employee
FOR EACH ROW
DECLARE
v_employee_id:=employee.employee_ID%TYPE;
BEGIN
SELECT employee_id INTO v_employee_id FROM employee;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001,'data not found');
END;
How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update.
My table estructure is:
employee_id NUMBER
employee_name VARCHAR(20)
employee_salary NUMBER
...
Thanks
You are on a wrong way. The trigger as it is will throw runtime 'Mutating table' error even after fixing syntax error - you missed semicolon after raise_application_error(also it should take 2 arguments, not one). Correct syntax :
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001, 'data not found'); -- 1st parameter -error code
Update
As far as I understand the updated version of the question, you want to show error if record doesn't exist. The problem with row level trigger approach is that it won't be executed if nothing is found due to condition in WHERE. The simplest way is to check number of rows affected on client side and raise an error there. Or you can write a procedure that checks sql%rowcount after executing desired update, and then throw an exception if it's 0.
If you prefer to do in a hard way, you can create package variable which of type employee.employee_ID%TYPE, before update statement level trigger that resets variable (say set it to null), after update row level trigger that sets this variable to NEW.employee_ID, and after update statement level trigger that throws an exception if the variable is null. Note: this will properly work for individual updates only.
"How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update."
There is really only one practical way to do this - use a referential constraint (foreign key).