Trigger error with after insert - sql

![enter image description here][1]I am creating a trigger that whenever I insert info into my table, I count the total row numbers and print the new added row. Here is my code:
Create or replace trigger TR_everyInsert
After INSERT On PERSONS
For each row
Declare
rowNumber int;
PERSON_NAME varchar(30);
gender varchar(30);
color varchar(30);
Begin
select PERSON_NAME,GENDER,COLOR
From PERSONS
Where PERSON_NAME=:new.PERSON_NAME;
select count(*) as rowNumber
from PERSONS;
if inserting then
DBMS_OUTPUT.PUT_LINE ('There are ' || To_CHAR(rowNumber));
DBMS_OUTPUT.PUT_LINE ('New added info is ' || PERSON_NAME || 'with gender ' ||
GENDER || 'with color ' || color);
end if;
end;
/
However, I got compile error saying "into clause expected", what is the problem please?

First, you can't have a PL/SQL block that just executes a SELECT. You need to do something with the data. If you expect the query to return exactly 1 row, do a SELECT INTO. If you expect the query to return more than 1 row, you'd want to open a cursor that you'd iterate over.
Second, in a row-level trigger, you cannot generally query the table itself. You'll generally end up with a mutating table exception (there are some special cases where you can do this but that severely limits your flexibility going forward so it's something that should be avoided). To get row-level information, just use the various columns from your :new pseudo-record. To get the count, you'd realistically want to use a statement-level trigger. Depending on the Oracle version, you could create a compound trigger that has row- and statement-level components as well.
Third, t doesn't really make sense to have an IF inserting statement in a trigger that is only defined on the insert operation. It would only make sense to have that sort of statement if your trigger was defined on multiple operations (say INSERT OR UPDATE) and you wanted to do something different depending on which operation caused the trigger to fire.
Finally, you'll want your local variables to be named something that is distinct from the names of any columns. Most people adopt some sort of naming convention to disambiguate local variables, package global variables, and parameters from column names. I prefer prefixes l_, g_, and p_ for local variables, package global variables, and parameters which is a reasonably common convention in the Oracle community. You may prefer something else.
Something like
-- A row-level trigger prints out the data that is being inserted
Create or replace trigger TR_everyInsert_row
After INSERT On PERSONS
For each row
Begin
DBMS_OUTPUT.PUT_LINE ('New added info is ' || :new.PERSON_NAME ||
' with gender ' || :new.GENDER ||
' with color ' || :new.color);
end;
-- A statement-level trigger prints out the current row count
Create or replace trigger TR_everyInsert_stmt
After INSERT On PERSONS
Declare
l_cnt integer;
Begin
select count(*)
into l_cnt
from persons;
DBMS_OUTPUT.PUT_LINE ('There are ' || To_CHAR(l_cnt) || ' rows.');
end;

The error message is pretty clear. You need to place the result of both your queries INTO the variables you declared:
Create or replace trigger TR_everyInsert
After INSERT On PERSONS
For each row
Declare
lv_rowNumber int;
lv-_PERSON_NAME varchar(30);
lv_gender varchar(30);
lv_color varchar(30);
Begin
select PERSON_NAME,GENDER,COLOR
into lv_person_name, lv_gender, lv_color
From PERSONS
Where PERSON_NAME=:new.PERSON_NAME;
select count(*) into lv_rowNumber
from PERSONS;
if inserting then
DBMS_OUTPUT.PUT_LINE ('There are ' || To_CHAR(rowNumber));
DBMS_OUTPUT.PUT_LINE ('New added info is ' || PERSON_NAME || 'with gender ' ||
GENDER || 'with color ' || color);
end if;
end;
/
I would advice you to give your variables different names than your columns. It could make the code confusing to read...

Related

Oracle - BULK COLLECT INTO VARRAY used with Bind Variables only collecting column headers

Quick Disclaimer:
First thing out of the way, I know the preferred way of handling dynamic SQL in Oracle now is the DBMS_SQL package but unfortunately my application team does not have the grants to execute these procs at the moment and I am hoping to get this quick workaround knocked out before our DBA team gets back to me. Also, this database is on Oracle 12c.
Script Goal: I recently developed a Stored Proc (let's call it Original) that uses values in a "control table" to make a large number of updates to certain columns in a database with many schemas and tables. This script I am struggling with now (let's call it Test) is meant to be a quick loop through those columns affected by Original so as to verify that everything worked expectedly. Ultimately, I want to output the top 5 results of each changed column and hand a spooled file to my testing team for validation.
The control_table used in both scripts has 4 columns and looks like this:
OWNER
TABLE_NAME
COLUMN_NAME
ALGORITHM
Schema1
TableA
ColumnA
Method1
Schema1
TableB
ColumnB
Method1
Schema2
TableC
ColumnC
Method2
An example of one of the tables that gets updated by Original (let's say for TableA above) would be:
OtherCol1
OtherCol2
ColumnA
OtherCol3
Ignored
Ignored
UpdatedData1
Ignored
Ignored
Ignored
UpdatedData2
Ignored
Ignored
Ignored
UpdatedData3
Ignored
Issue with Test script: I have the dynamic SQL - I believe - working as it needs and I have been trying to figure out how best to print the results of the EXECUTE IMMEDIATE command to output. In doing some reading, I found that BULK COLLECT INTO should allow me to store the results of the dynamic queries into a COLLECTION which I can then print with dbms_output. I have attempted to do this with both a TABLE and a VARRAY but in both cases when I print, I am finding that the data stored in my collection is the column header of my dynamic query instead of the query values! The only thing I can think that could be the problem is the combining of BULK COLLECT INTO with the USING command when I run the dynamic statement but I have seen nothing in the documentation to indicate that these two commands are incompatible and my Test procedure below compiles without issue (and even seems to run ok).
Test Script:
SET SERVEROUTPUT ON SIZE UNLIMITED;
DECLARE
l_script VARCHAR2(500);
l_errm VARCHAR2(64);
TYPE results IS VARRAY(5) OF VARCHAR2(250);
va_cols results; --Defining here with a VARRAY but I have also tried with a table
BEGIN
FOR c_col IN(
SELECT owner, table_name, column_name, algorithm FROM control_list)
LOOP
l_errm := NULL;
va_cols := NULL;
BEGIN
dbms_output.put_line('Column '|| c_col.column_name || ' of table ' || c_col.owner ||
'.' || c_col.table_name || ' used algorithm ' || c_col.algorithm);
l_script := 'SELECT :1 FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY';
dbms_output.put_line('Script sent to Exec Immediate: ' || l_script); --Print l_script for debugging
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols USING c_col.column_name, c_col.column_name;
dbms_output.put_line(va_cols(1));
dbms_output.put_line(va_cols(2));
dbms_output.put_line(va_cols(3));
dbms_output.put_line(va_cols(4));
dbms_output.put_line(va_cols(5));
EXCEPTION
WHEN OTHERS THEN
l_errm := SUBSTR(SQLERRM, 1, 64);
dbms_output.put_line(' ERROR: ' || l_errm || '. Skipping row');
CONTINUE;
END;
END LOOP;
END;
/
So my intended dbms_output of the script above is:
Column ColumnA of table Schema1.TableA used algorithm Method1
Script sent to Exec Immediate: SELECT :1 FROM SCHEMA1.TABLEA WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY
UpdatedData1
UpdatedData2
UpdatedData3
UpdatedData4
UpdatedData5
Instead, however, bizarrely, what I am getting when I run this is:
Column ColumnA of table Schema1.TableA used algorithm Method1
Script sent to Exec Immediate: SELECT :1 FROM SCHEMA1.TABLEA WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY
ColumnA
ColumnA
ColumnA
ColumnA
ColumnA
Has anyone seen this before and know what I am doing wrong? Thanks in advance!!
You can't use bind variables to change what columns you're referencing. You use bind variables to specify particular values at runtime. When you do
l_script := 'SELECT :1 FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY';
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols USING c_col.column_name, c_col.column_name;
you're telling Oracle that you want to select the literal string in the variable c_col.column_name. Not the column in the table by that name. Which is why every row returns that literal value.
You'd need to dynamically assemble the SQL statement with the column names, not try to use them as bind variables. So something like
l_script := 'SELECT ' || c_col.column_name ||
' FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE ' || c_col.column_name || ' IS NOT NULL FETCH FIRST 5 ROWS ONLY';
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols;
This is approximately what you want. I outer cursor over tables and column to inspect that generate the dynamic SQL.
Inner loop reading the column values from the previous query
DECLARE
TYPE CurTyp IS REF CURSOR;
v_cursor CurTyp;
v_value VARCHAR2(200);
v_stmt_str VARCHAR2(200);
BEGIN
FOR c IN (
SELECT table_name, column_name FROM control_list)
LOOP
dbms_output.put_line('tab: '||c.table_name);
v_stmt_str := 'SELECT '||c.column_name||' FROM '|| c.table_name;
OPEN v_cursor FOR v_stmt_str;
LOOP
FETCH v_cursor INTO v_value;
EXIT WHEN v_cursor%NOTFOUND;
dbms_output.put_line('col: '||c.column_name||' val: '||v_value);
END LOOP;
END LOOP;
CLOSE v_cursor;
END;
/

Execute immediate select returns no values

I have select statement
select name, surname
from student
where id_student = 1;
It returns
name surname
Bob Smith
I want to create procedure with same select statement, using execute immediate:
create or replace procedure select_procedure
as
begin
execute immediate
'select name, surname
from student
where id_student = 1';
end;
/
exec select_procedure;
When this procedure is executed it shows PL/SQL procedure successfully completed. How do I get the result? (set serveroutput is on)
You have to select into something. If you don't then the query isn't even executed (though it is parsed).
create or replace procedure select_procedure
as
l_name student.name%TYPE;
l_surname student.name%TYPE;
begin
execute immediate
'select name, surname
from student
where id_student = 1'
into l_name, l_surname;
end;
/
But, in no particular order: (a) you should use bind variables instead of having the literal value 1 embedded in the dynamic statement; (b) this doesn't need to be dynamic at all; and (c) the caller won't be able to see the values returned by the query anyway - unless you select into OUT arguments instead, or display them with dbms_output() (although that should really only be used for debugging as you can't control whether the client will show it).
So you could do:
create or replace procedure select_procedure
as
l_name student.name%TYPE;
l_surname student.name%TYPE;
begin
select name, surname
into l_name, l_surname
from student
where id_student = 1;
dbms_output.put_line('name=' || l_name ||', surname=' || l_surname);
end;
/
or
create or replace procedure select_procedure (
p_name OUT student.name%TYPE,
p_surname OUT student.name%TYPE
)
as
begin
select name, surname
into p_name, p_surname
from student
where id_student = 1;
end;
/
and have your caller pass in its own variable names to populate, and then do whatever it needs with those. The caller would usually also pass in the ID you're looking for, so you don't have the 1 hard-coded.
It doesn't seem like a procedure is really the best mechanism for this though.
Also, using a select ... into (static or dynamic) will error if the query returns zero rows or more than one row. It will only work if there is exactly one row returned. A cursor would handle any number of rows - but unless you are just printing the results (as #Jayanth shows) you need to pass the cursor back to the caller instead. You could do a bulk collect into a collection instead, but you still have to do something with that.
You have to write a cursor for this.Please find below the syntax for the same.
Syntax:
create or replace procedure select_procedure
as
CURSOR <cursor_name> IS <SELECT statement without semicolon>;
BEGIN
FOR record IN <cursor_name>
LOOP
Dbms_output.put_line(‘Record Fetched:‘||record.name);
Dbms_output.put_line(‘Record Fetched:‘||record.surname);
END LOOP;
END;

SQL Trigger time check + - 1hour [duplicate]

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;

PL/SQL Dynamic Loop Value

My goal is to keep a table which contains bind values and arguments, which will later be used by dbms_sql. The below pl/sql example is basic, it's purpose is to illustrate the issue I am having with recalling values from prior loop objects.
The table account_table holds acccount information
CREATE TABLE account_table (account number, name varchar2(100)));
INSERT INTO mytest
(account, name)
VALUES
(1 ,'Test');
COMMIT;
The table MYTEST holds bind information
CREATE TABLE mytest (bind_value varchar2(100));
INSERT INTO mytest (bind_value) VALUES ('i.account');
COMMIT;
DECLARE
v_sql VARCHAR2(4000) := NULL;
v_ret VARCHAR2(4000) := NULL;
BEGIN
FOR I IN (
SELECT account
FROM account_table
WHERE ROWNUM = 1
) LOOP
FOR REC IN (
SELECT *
FROM mytest
) LOOP
v_sql := 'SELECT ' || rec.bind_value || ' FROM dual';
EXECUTE IMMEDIATE v_sql INTO v_ret;
dbms_output.put_line ('Account: ' || v_ret);
END LOOP;
END LOOP;
END;
/
I cannot store the name i.account and later use the value that object. My idea was to use NDS; but, while the value of v_sql looks ok (it will read "Select i.account from dual"), an exception of invalid identifier would be raised on i.account. Is there a way to get the value of the object? We are using Oracle 11g2.
Thanks!
Dynamic SQL will not have the context of your PL/SQL block. You cannot use identifiers that are defined in the PL/SQL block in your dynamic SQL statement. So you cannot reference i.account and reference the loop that you have defined outside of the dynamic SQL statement. You could, of course, dynamically generate the entire PL/SQL block but dynamic PL/SQL is generally a pretty terrible approach-- it is very hard to get that sort of thing right.
If you are trying to use the value of i.account, however, you can do something like
v_sql := 'SELECT :1 FROM dual';
EXECUTE IMMEDIATE v_sql
INTO v_ret
USING i.account;
That doesn't appear to help you, though, if you want to get the string i.account from a table.
Are you always trying to access the same table with the same where clause, but just looking for a different column each time? If so, you could do this:
p_what_I_want := 'ACCOUNT';
--
SELECT decode(p_what_I_want
,'ACCOUNT', i.account
, 'OTHER_THING_1', i.other_thing_1
, 'OTHER_THING_2', i.other_thing_2
, 'OTHER_THING_1', i.default_thing) out_thing
INTO l_thing
FROM table;
The above statement is not dynamic but returns a different column on demand...

how to use a procedure parameter in a query

how do i access the procedure parameters inside the same procedure using a query
for example: see this procedure
procedure game(left in tab.left%type,right in tab.right%type,...)
is
--some local variables
begin
merge into tgt_table
using subquery --(here is what i need to use the parameters)
on some condition
when matched then
update the tgt table
when not matched then
insert the table;
end game;
In above procedure and in merge statement, i need a query such that it uses the parameters value as a table reference and using those values it either updates or inserts into the table based on the condition given.
Help me please. Thanks in advance
You would need to use dynamic SQL if your parameters define the table to use - something like:
procedure game(left in tab.left%type,right in tab.right%type,...)
is
--some local variables
l_sql long;
begin
l_sql := 'merge into tgt_table'
|| ' using ' || left
|| ' on some condition'
|| ' when matched then'
|| ' update ' || right
|| ' when not matched then'
|| ' insert the table';
execute immediate l_sql;
end game;
However, you have a lot more work left to do, since the condition, update and insert clauses all need to change according to the tables being used. I'm not convinced this procure will be particularly useful in fact.
Please read http://download.oracle.com/docs/cd/B14117_01/appdev.101/b10807/06_ora.htm#sthref777