Trigger created with compilation errors in oracle - sql

I am learning ORACLE and haven't been able to figure out why i am getting compilation errors when trying to create this trigger. Thank you for any help!
CREATE OR REPLACE TRIGGER TR_HISTORY
BEFORE INSERT ON HISTORY
FOR EACH ROW
DECLARE name_d varchar2(50), breed_d varchar2(50), area_d varchar2(50)
BEGIN
SELECT NAME INTO name_d FROM ANIMALS A WHERE A.ID = :NEW.ID;
SELECT BREED INTO breed_d FROM ANIMALS A WHERE A.ID = :NEW.ID;
SELECT AREA INTO area_d FROM STORE S WHERE S.STORE_ID = :NEW.STORE_ID;
IF (:NEW.DONE ='T')
THEN
:NEW.MSG = 'Hi , your animal ' || name_d || ' breed: ' || breed_d || 'is
at ' || area_d || '.';
ELSE
UPDATE :NEW.MSG = 'Not finished';
END IF;
END;
/

There were few issues with your Trigger code syntax.
we use semicolons ';' after each expression in declare not comma
','
Check the proper syntax for IF Condition.
THEN UPDATE
:NEW.MSG =
Is not a valid statement. It is simply :NEW.MSG :=
CREATE OR replace TRIGGER tr_history
BEFORE INSERT ON history
FOR EACH ROW
DECLARE
name_d VARCHAR2(50);
breed_d VARCHAR2(50);
area_d VARCHAR2(50);
BEGIN
SELECT name
INTO name_d
FROM animals A
WHERE A.id = :NEW.id;
SELECT breed
INTO breed_d
FROM animals A
WHERE A.id = :NEW.id;
SELECT area
INTO area_d
FROM store S
WHERE S.store_id = :NEW.store_id;
IF :NEW.done = 'T' THEN
:NEW.msg := 'Hi , your animal '
|| name_d
|| ' breed: '
|| breed_d
|| 'is at '
|| area_d
|| '.';
ELSE
:NEW.msg := 'Not finished';
END IF;
END;
/

Related

ORA-04091 TABLE ODB.EMPLOYEE IS MUTATING, TRIGGER/FUNCTION MAY NOT SEE IT. IS THERE SOMETHING WRONG WITH MY TRIGGER?

Trying to create a trigger when there is an update of Status on Employee Table and capture some values for the record in Employee table and Employee_Header table for that record and send an email. The trigger throws an error.
CREATE OR REPLACE TRIGGER ODB.TRG_EMPLOYEE_STATUS_EMAIL
AFTER UPDATE OF STATUS ON ODB.EMPLOYEE
FOR EACH ROW
DECLARE
s_message varchar2(4000);
s_subject varchar2(1000);
s_return_message varchar2(4000);
s_database varchar2(50);
v_rm EMPLOYEE%ROWTYPE;
v_sh EMPLOYEE_HEADER%ROWTYPE;
BEGIN
if :old."STATUS" = 'HOLD' AND :new."STATUS" = 'ACTIVE' AND :new."CATEGORY" = 'FULLTIME' then
select * into v_rm from EMPLOYEE WHERE :new."STATUS" = 'ACTIVE' AND ROWNUM>1;
select * into v_sh from EMPLOYEE_HEADER WHERE ROWNUM>1;
s_subject := 'NAME ' || v_rm.NAME ||' message ' || ' CHECK LOG OF EMPLOYEE' || Chr(13) || ' STATUS: ' || v_rm.STATUS ;
s_message := 'SAMPLE' || Chr(10)||Chr(13) || 'THE STATUS IN EMPLOYEE_HEADER IS: ' || Chr(10)|| ' STATUS: ' || v_sh.STATUS ;
pkg_email.sendEmail('INPUT PARAMETERS TO SEND EMAIL');
end if;
END;
You can't select from a table which is just being changed; it is mutating. Though, as you can use the :new pseudorecord, you can "skip" that error. Also, where rownum > 1 is useless as it is never true. I don't know what you meant to say by using it.
I see you've created columns using double quotes. In Oracle, that's usually a mistake. Not that it won't work - it will, but you always have to reference columns using double quotes and match letter case.
Finally, trigger might look like this (read comments within code):
create or replace trigger odb.trg_employee_status_email
after update of status on odb.employee
for each row
declare
s_message varchar2(4000);
s_subject varchar2(1000);
s_return_message varchar2(4000);
s_database varchar2(50);
-- v_rm employee%rowtype; -- you don't need that
v_sh employee_header%rowtype;
begin
if :old."status" = 'HOLD' and :new."status" = 'ACTIVE' and
:new."category" = 'FULLTIME'
then
-- You can't select from a table which is just being changed - it is "mutating".
-- Besides, AND ROWNUM > 1 will never return anything. You can only check
-- ROWNUM <= some_value
--select * into v_rm from employee where :new."status" = 'ACTIVE' and rownum>1;
select * into v_sh from employee_header where rownum>1;
-- instead of SELECT ... INTO V_RM, use :NEW pseudorecord
s_subject := 'NAME ' || :new.name ||' message ' || ' CHECK LOG OF EMPLOYEE'
|| chr(13) || ' STATUS: ' || :new.status ;
s_message := 'SAMPLE' || chr(10)||chr(13) || 'THE STATUS IN EMPLOYEE_HEADER IS: ' || chr(10)|| ' STATUS: ' || v_sh.status ;
pkg_email.sendemail('INPUT PARAMETERS TO SEND EMAIL');
end if;
exception
when no_data_found then null;
end;

Count how many car/cars owner owns

I'm trying to learn plsql and got stuck in understanding some basic stuff. Here is a challenge that I'm trying to solve. I have two tables. One holds information about owners and the other is information about cars.
I want to to write an anonymous block that joins these two tables and with a for loop based on amount of cars that is registered to each owner prints how many cars each person own. furthermore I want an if statement which distinguishes between 1 Car (singular) and 2, 3 Cars (plural).
the tables are these:
CREATE TABLE owners(
id_nr VARCHAR2(13) PRIMARY KEY,
f_name VARCHAR2(20),
s_name VARCHAR2(20)
);
CREATE TABLE cars(
reg_nr VARCHAR2(6) PRIMARY KEY,
id_nr REFERENCES owners(pnr),
model VARCHAR2(20),
year NUMBER(4),
date DATE
);
The result may look like something like this:
19380321-7799, Hans, Anderson, Owns: 1 car
19490321-7899, Mike, Erikson, Owns: 2 cars
. . . etc
I know the this question was already answered but when I try following:
declare
v_suffix varchar2(1);
begin
for o in (select bilägare_pnr, fnamn, enamn,
(select count(1) from fordon where fordon_pnr = bilägare_pnr) as bilar_ägda
from bilägare)
loop
if o.pnr_fordon = 1
then v_suffix = 'bil'
else v_suffix = 'bilar'
end if;
dbms_output.put_line(o.pnr || ', ' || o.fnamn || ', ' || o.enamn
|| ' Äger: ' || o.pnr_fordon || ' bil' || v_suffix);
end loop;
end;
/
I get:
ORA-06550: line 9, column 27:
PLS-00103: Encountered the symbol "=" when expecting one of the following:
:= . ( # % ;
any tips? Im not sure how to declare v_suffix
EDIT (copied from comment on answer below):
Updating my code:
declare
cursor c_BILÄGARE is
select fnamn,enamn,pnr
from BILÄGARE;
begin
for rec in c_BILÄGARE loop
if (rec.antal>1) then
dbms_output.put_line (rec.pnr||','|| rec.fnamn || ',' ||
rec.enamn || ',' || rec.antal || 'bilar');
else
dbms_output.put_line (rec.pnr||','|| rec.fnamn || ',' ||
rec.enamn || ',' || rec.antal || 'bil');
end if;
end loop;
end;
getting:
ORA-06550: line 9, column 9: PLS-00302: component 'ANTAL' must be declared (antal=Quantity)
As noted above, you need to use := as the assignment operator. You also need a semi-colon after each statement - thus, you should use
then v_suffix := 'bil';
instead of
then v_suffix := 'bil'
So your code should look like:
declare
v_suffix varchar2(1);
begin
for o in (select bilägare_pnr, fnamn, enamn,
(select count(1)
from fordon
where fordon_pnr = bilägare_pnr) as bilar_ägda
from bilägare)
loop
if o.pnr_fordon = 1
then v_suffix := 'bil';
else v_suffix := 'bilar';
end if;
dbms_output.put_line(o.pnr || ', ' || o.fnamn || ', ' || o.enamn
|| ' Äger: ' || o.pnr_fordon || ' bil' || v_suffix);
end loop;
end;
Please try this piece of code:
begin
for rec in (select
o.id_nr || ',' || o.f_name || ',' || o.s_name || ', Owns: ' || coalesce(car.cnt, 0) || ' ' || decode(car.cnt , 1, 'Car', 'Cars') as res
from owners o
left outer join (select id_nr, count(1) as cnt from cars group by id_nr) car on (car.id_nr = o.id_nr)) loop
dbms_output.put_line(rec.res);
end loop;
end;
Thanks.
There are multiple issues in your code which is highlighted and fixed in following code:
declare
v_suffix varchar2(5); -- data length should be 5 or more
begin
for o in (select bilägare_pnr, fnamn, enamn,
(select count(1) from fordon where fordon_pnr = bilägare_pnr) as bilar_ägda
from bilägare)
loop
if o.bilar_ägda <= 1 -- replaced it from pnr_fordon and <= is used for 0 or 1 car
then v_suffix := 'bil'; -- := and ; is used here and in next statement
else v_suffix := 'bilar';
end if;
dbms_output.put_line(o.pnr || ', ' || o.fnamn || ', ' || o.enamn
|| ' Äger: ' || o.pnr_fordon || ' bil' || v_suffix);
end loop;
end;
/

Update column data to null without writing each column name in sql

I have a table XX_LOCATION with 20 columns. Out of this I want data in only 4 columns in rest 16 I want to update the column values to null.
How can 1 update statement be used for this i.e. I don't want to write each column name of 16 columns in the update statement. Is there any other way out?
Ok, I admit. I am having a little bit of fun with this one.
BEGIN
FOR eachrec IN (SELECT column_name
FROM user_tab_cols a
WHERE table_name = 'MYTABLE'
AND column_name NOT IN ('COL1', 'COL2', 'COL3'
, 'COL4'))
LOOP
execute immediate 'update XX_LOCATION set ' || eachrec.column_name || ' = null';
END LOOP;
END;
Here is another possibility, this one with only a single execute immediate:
DECLARE
l_cmd VARCHAR2 (2000);
l_comma VARCHAR2 (1);
BEGIN
l_cmd := 'update xx_location set ';
FOR eachrec IN (SELECT column_name
FROM user_tab_cols a
WHERE table_name = 'MYTABLE'
AND column_name NOT IN ('COL1', 'COL2', 'COL3'
, 'COL4'))
LOOP
l_cmd := l_cmd || l_comma || eachrec.column_name || ' = null';
l_comma := ',';
END LOOP;
execute immediate l_cmd;
END;
And finally, a completely automated solution to exclude N columns, without having to type the non-specified column names:
CREATE TYPE column_tt IS TABLE OF VARCHAR2 (30);
CREATE OR REPLACE PROCEDURE nullify_columns (
p_owner IN all_tab_cols.owner%TYPE
, p_table IN all_tab_cols.table_name%TYPE
, p_exclude_columns IN OUT NOCOPY column_tt
)
AS
l_cmd VARCHAR2 (2000);
l_comma VARCHAR2 (1);
BEGIN
l_cmd := 'update ' || p_owner || '.' || p_table || ' set ';
FOR eachrec IN (SELECT column_name
FROM all_tab_cols a
WHERE owner = p_owner
AND table_name = p_table)
LOOP
IF NOT eachrec.column_name MEMBER OF p_exclude_columns
THEN
l_cmd := l_cmd || l_comma || eachrec.column_name || ' = null';
l_comma := ',';
END IF;
END LOOP;
EXECUTE IMMEDIATE l_cmd;
END;
CREATE TABLE totally_bogus_dude
(
col1 VARCHAR2 (10)
, col2 VARCHAR2 (10)
, col3 VARCHAR2 (10)
, col4 VARCHAR2 (10)
, col5 VARCHAR2 (10)
);
DECLARE
l_exclude column_tt := column_tt ();
BEGIN
l_exclude.EXTEND;
l_exclude (l_exclude.COUNT) := 'COL1';
l_exclude.EXTEND;
l_exclude (l_exclude.COUNT) := 'COL2';
l_exclude.EXTEND;
l_exclude (l_exclude.COUNT) := 'COL3';
l_exclude.EXTEND;
l_exclude (l_exclude.COUNT) := 'COL4';
FOR eachrec IN (SELECT COLUMN_VALUE
FROM TABLE (l_exclude))
LOOP
DBMS_OUTPUT.put_line (eachrec.COLUMN_VALUE);
END LOOP;
nullify_columns (p_owner => USER, p_table => 'TOTALLY_BOGUS_DUDE', p_exclude_columns => l_exclude);
END;

Cursor inside procedure body

I am trying to declare a cursor inside procedure body.
I know it is supposed to be done in the declare block but the table the cursor refers is created inside the procedure body.
--TABLE MAY OR MAY NOT BE PRESENT PRIOR TO PROCEDURE EXECUTION
SELECT COUNT(*)
INTO ln_cnt
FROM User_Tables
WHERE table_name = 'TMP$UOM_COMBO_GEN';
IF ln_cnt > 0 THEN
EXECUTE IMMEDIATE ' CREATE TABLE TMP$UOM_COMBO_GEN (UOM_ID VARCHAR2(20 BYTE), HIER_CODE VARCHAR2(20 BYTE),NODE_CODE VARCHAR2(200 BYTE))';
END IF;
CURSOR C_HIER
IS
SELECT DISTINCT HIER_CODE FROM TMP$UOM_COMBO_GEN WHERE UOM_ID=P_UOM_ID;
FOR HIER IN C_HIER
LOOP
IF C_HIER%ROWCOUNT = 1 THEN
LV_SQL2 := '(SELECT UOM_ID, NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE;
LV_SORT := ' ORDER BY '||HIER.HIER_CODE||'';
LV_SQL := 'SELECT * FROM ' || LV_SQL2;
ELSE
LV_SQL3 := ' LEFT OUTER JOIN(SELECT NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE ||' ON 1=1';
LV_SORT := LV_SORT||','||HIER.HIER_CODE||'';
LV_SQL := LV_SQL || LV_SQL3;
END IF;
END LOOP;
I am getting the following error.
Error(17,10): PLS-00103: Encountered the symbol "C_HIER" when expecting one of the following: := . ( # % ;
Well a table once created is stored in database and u can refer it from wherever u want to refer in the schema .
Also there a change may be required in your code in the following part
SELECT COUNT(*)
INTO ln_cnt
FROM User_Tables
WHERE table_name = 'TMP$UOM_COMBO_GEN';
IF ln_cnt > 0 THEN -- means only if table exists u want to create the table which will
--throw an exception if table is already there ,
--so better equate it to 0
EXECUTE IMMEDIATE ' CREATE TABLE TMP$UOM_COMBO_GEN (UOM_ID VARCHAR2(20 BYTE), HIER_CODE VARCHAR2(20 BYTE),NODE_CODE VARCHAR2(200 BYTE))';
END IF;
Now , if u really have a requirement to create a new table every time some condition is true/false and u then want to select the table in a cursor do something like following by using reference cursors
create or replace procedure abc(Table_name varchar2 , param_list varchar2 , where_clause varchar2) is
c_hier sys_refcursor ;
LV_SQL2 varchar2(2000) ;
LV_SORT varchar2(2000) ;
LV_SQL varchar2(2000) ;
LV_SQL3 varchar2(2000) ;
begin
SELECT COUNT(*)
INTO ln_cnt
FROM User_Tables
WHERE table_name = Table_name; -- Use any table here
IF ln_cnt = 0 THEN
EXECUTE IMMEDIATE ' CREATE TABLE '||Table_name||' '||param_list;
END IF;
open c_hier for 'SELECT DISTINCT '||param_list||' FROM '||table_name||' '||where_clause;
FOR HIER IN C_HIER
LOOP
IF C_HIER%ROWCOUNT = 1 THEN
LV_SQL2 := '(SELECT UOM_ID, NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE;
LV_SORT := ' ORDER BY '||HIER.HIER_CODE||'';
LV_SQL := 'SELECT * FROM ' || LV_SQL2;
ELSE
LV_SQL3 := ' LEFT OUTER JOIN(SELECT NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE ||' ON 1=1';
LV_SORT := LV_SORT||','||HIER.HIER_CODE||'';
LV_SQL := LV_SQL || LV_SQL3;
END IF;
END LOOP;

oracle xmltable with columns from another table

with oracle xmltable
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS contract integer path 'contract',
oper VARCHAR2(20) PATH 'oper' ) u
This is normally what we do.
Now I need to have "COLUMNS" in above query selected from another tables column for Xpath
something like
{
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS (select xpath from xpath_metadeta )) u
}
Please let me know if this is possible and how?
One option that comes to my mind is dynamic sql and ref cursor.
Something like this:
DECLARE
columnParameters SYS.ODCIVARCHAR2LIST :=
SYS.ODCIVARCHAR2LIST(
'TITLE VARCHAR2(1000) PATH ''title''',
'SUMMARY CLOB PATH ''summary''',
'UPDATED VARCHAR2(20) PATH ''updated''',
'PUBLISHED VARCHAR2(20) PATH ''published''',
'LINK VARCHAR2(1000) PATH ''link/#href'''
);
ref_cursor SYS_REFCURSOR;
cursor_id NUMBER;
table_description DBMS_SQL.DESC_TAB;
column_count NUMBER;
string_value VARCHAR2(4000);
clob_value CLOB;
FUNCTION DYNAMIC_XMLTABLE(xml_columns SYS.ODCIVARCHAR2LIST) RETURN SYS_REFCURSOR
IS
result SYS_REFCURSOR;
statementText VARCHAR2(32000) := Q'|SELECT * FROM
XMLTABLE(
XMLNAMESPACES (DEFAULT 'http://www.w3.org/2005/Atom'),
'for $entry in /feed/entry return $entry'
PASSING
HTTPURITYPE('http://stackoverflow.com/feeds/tag?tagnames=oracle&sort=newest').getxml()
COLUMNS
{column_definition}
)|';
BEGIN
SELECT REPLACE(statementText, '{column_definition}', LISTAGG(COLUMN_VALUE, ', ') WITHIN GROUP (ORDER BY ROWNUM)) INTO statementText FROM TABLE(xml_columns);
DBMS_OUTPUT.PUT_LINE('Statement: ' || CHR(10) || statementText);
OPEN result FOR statementText;
RETURN result;
END;
BEGIN
DBMS_OUTPUT.ENABLE(NULL);
ref_cursor := dynamic_xmltable(columnParameters);
cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(ref_cursor);
DBMS_SQL.DESCRIBE_COLUMNS(cursor_id, column_count, table_description);
FOR i IN 1..column_count LOOP
IF table_description(i).col_type = 1 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, string_value, 4000);
ELSIF table_description(i).col_type = 112 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, clob_value);
END IF;
END LOOP;
WHILE DBMS_SQL.FETCH_ROWS(cursor_id) > 0 LOOP
FOR i IN 1..column_count LOOP
DBMS_OUTPUT.PUT_LINE(table_description(i).col_name || ': datatype=' || table_description(i).col_type);
IF (table_description(i).col_type = 1) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, string_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || string_value);
END;
ELSIF (table_description(i).col_type = 112) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, clob_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || clob_value);
END;
-- add other data types
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cursor_id);
END;
I depends how the cursor is consumed. It's much simple if by an application, a bit more difficult if using PL/SQL.