INSERT inside plsql procedure does not tell how many rows were inserted - plsql

i am trying to insert some rows and update some rows inside a pl/sql loop.
however all i get to see is the pl/sql procedure is successfully completed.
i do get to see dbmbs_ouput statements but not the output status of insert and/or update queries.
the serveroutput is set to on.
how do i get to see the status of insert and update rows(namely how many rows were inserted and updated)

In Oracle, the rowcount is not output automatically like it is in SQL Server.
You should do it explicitly:
BEGIN
INSERT
INTO mytable
SELECT …
FROM other_table;
DBMS_OUTPUT.put_line(SQL%ROWCOUNT);
END;

Related

Mutating table trigger error for one type of insert statement

I'm getting mutating table error for statement insert into employee select 'xyz',200 from dual and scripts executes successfully for insert into employee values ('abc',100);.
Can somebody explain why the statement fails for one type of insert statement? Both scripts insert similar type of data into table
details of script:
--table creation
create table employee (name varchar2(30),salary number);
--trigger creation
create or replace trigger emp_trig
before insert on employee
for each row
begin
delete from employee where name=:new.name;
end;
/
--insert statement 1
insert into employee values ('abc',100);
--result : 1 row inserted
--insert statement 2
insert into employee select 'xyz',200 from dual
--result:
Error report -
ORA-04091: table NMS_CON.EMPLOYEE is mutating, trigger/function may not see it
ORA-06512: at "NMS_CON.EMP_TRIG", line 2
ORA-04088: error during execution of trigger 'NMS_CON.EMP_TRIG'
Inserting a single row will not lead to a mutating table error - how could it, since that row wasn't there before?
But insert-select potentially involves more than one row, so then you get the error.
Generally, you should not have non-query DML operations in your trigger. Too many possible side effects and undesirable consequences.
A better approach is to write a procedure that will do the insert for you, do not give insert privileges on the table directly, only to the package that owns the procedure. Then inside that procedure you can do a delete before your insert, or you can do a merge - or whatever.
All the logic is hidden inside the procedure and by restricting privs on the table, you ensure that the procedure must be called.
Hope that helps!

SQLite locking specific columns

I am trying to lock specific columns in a table, in order to prevent from a mistakely UPDATE command to modify those fields. On the contrary, some other columns should be allowed to be modified. Is there any specific SQL command for doing so?
You can use a trigger to prevent this:
CREATE TRIGGER PreventUpdateOfThisThat
BEFORE UPDATE OF ThisColumn, ThatColumn OF MyTable
BEGIN
SELECT RAISE(FAIL, "don't do that!");
END;

Trigger compiled successfully but still not firing as desired

Here is my simple PL/SQL code for demonstration purpose.
create table cust(cname varchar(10));
set SERVEROUTPUT ON;
create or replace trigger tgr
before insert on cust
for each row
enable
begin
dbms_output.put_line('Trigger hit on insert');
end;
/
insert into cust values('John');
OUTPUT:/
Table CUST created
Trigger TGR compiled
1 row inserted // *EXPECTING* Trigger hit on insert
You can't use DBMS_OUTPUT.PUT_LINE because when you insert data to your table, there is no prompt or screen which shows the execution of trigger. this applied same for procedures , functions , triggers.
Don't use commit inside a table. it will throw an exception. if you want to use commit then make the trigger pragma autonomous_transaction (i suggest not to use commit.it will automatically commit) .
Also make sure you don't modify the same table in the trigger. it will also throw and exception for mutating the data.
sample code
create table cust(cname varchar(10));
create table log(log varchar(10));
create or replace trigger tgr
before insert on cust
for each row
enable
begin
insert into log values('test');
end;
/
Don't use DBMS_OUTPUT.PUT_LINE inside a trigger to check if it was fired or not. Some of the IDEs like SQL developer might not output the message , even if you use SET SERVEROUTPUT ON.
However it could work on sqlplus . When i tried, it did display the message in sqlplus. So, it is not an issue with the database or your trigger.
SQL> insert into cust values('John');
Trigger hit on insert
And if you issue commit or rollback on the transaction, the messages might appear in sql developer.
I would suggest , it is better if you can create a log table and try to insert records into it rather than use dbms_output.
begin
INSERT INTO LOG_TABLE ( log_date,log_message) VALUES (SYSDATE,'Trigger hit on insert');
end;
/

Reusing large SQL queries in stored procedures

I have a number of long SQL select queries( 150 lines+) that I want to use in a PL/SQL package. The package has procedures to execute the SQL queries and insert the results into a separate table, compare the SQL results to another table, delete rows etc
Its fairly easy to store the SQL results with:
INSERT into TABLE1
SELECT .... (150 line ugly select query goes here)
Problem is, I want to store the select SQL in a cursor/function/view/whatever-works so I don't have to paste the 150 lines query into each procedure where the SQL is used.
I can store the SQL as a cursor then loop through the cursor within a package procedure, fetching each row and eg inserting into my table. But this seems very inefficient considering my only motivation for using a cursor is reducing the amount of lines my package.
Is there a better way to call the SQL select query in different procedures without copying & pasting all 150 lines? If this was a script, I would store the SQL in a text file then just read the text file into a variable and pass the variable to sqlplus when needed. But I'm not very familiar with PL/SQL.
Code:
CREATE OR REPLACE PACKAGE BODY MyPackage
as
Cursor my_cursor
select (150+ lines goes here)
PROCEDURE PopulateTable
is
TYPE fetch_array IS TABLE OF my_cursor%ROWTYPE;
s_array fetch_array;
BEGIN
open my_cursor;
LOOP
FETCH tran_cursor BULK COLLECT INTO s_array;
FORALL counter in 1..s_array.COUNT
INSERT INTO my_table VALUES s_array(counter);
EXIT when s_array%NOTFOUND;
END LOOP;
close my_cursor;
COMMIT;
END PopulateTable;
END MyPackage;
I am not sure if this would be the best way to do, but what came to my mind, is a variable cursor. You could do that using SYS_REFCURSOR. You can build a function that contains your query, and returns ref curosr. In all your procedures, you can just call that function. This will save you writing 150+ lines query in every procedure. More important, it will limit your program to one copy of the query, and therefore easy to maintain.
The function that returns the ref cursor, could be something like this:
CREATE OR REPLACE FUNCTION my_ugly_query()
RETURN SYS_REFCURSOR
AS
my_cursor_ref SYS_REFCURSOR;
BEGIN
OPEN my_cursor_ref FOR
SELECT -- 150+ lines of query;
RETURN my_cursor_ref;
END;
This is how to use it:
CREATE OR REPLACE PACKAGE BODY MyPackage
as
PROCEDURE PopulateTable
IS
l_cur_refcur SYS_REFCURSOR;
s_array fetch_array;
BEGIN
l_cur_refcur := my_ugly_query();
LOOP
FETCH tran_cursor BULK COLLECT INTO s_array;
EXIT when s_array%NOTFOUND;
FORALL counter in 1..s_array.COUNT
INSERT INTO my_table VALUES s_array(counter);
END LOOP;
CLOSE my_cursor;
COMMIT;
END PopulateTable;
END MyPackage;
Create your cursor in the package specification rather than the package body. You may then refer to it from any package procedure/function using package_name.cursor_name

Select for Update sql is a read and write mode?

I have simultaneous request to a particular row in a table and PL/SQL statement is used to update the table by reading the data from master row in the same table and update the current range row and master row it read.
Algorithm is like this:-
Declare
variable declaration
BEGIN
Select (Values) into (values1) from table where <condition1> for update;
select count(*) into tempval from table where <condition2>;
if (tempval == 0) then
insert into table values(values);
else
select (values) into (values2) from table where <condition2> for update;
update table set (values1) where <condition2>;
end if
update table set (values1+incrval) where <condition1>
END;
Unfortunately the master row is updated properly with the correct sequence but the current range picks up the old value of the master range. It does the dirty read. Even though the transaction isolation level for the table is serialized.
Please could some tell me what is happening here?
This is working as designed. Oracle default, and only, read isolation lets the session see all of their own updates. If you perform:
INSERT INTO TABLE1 (col1) values (1);
COMMIT;
UPDATE TABLE1 SET col1 = 2 where col1 = 1;
SELECT col1 FROM TABLE1;
you will see 2 returned from the last query. Please read the Merge Explanation for how to use a MERGE statement to perform the insert or update based upon a single criteria.

Resources