TRIGGER | FOR LOOP OR CURSOR USING FOR VARIABLE PL/SQL - oracle

I want to try insert loghis of table into one column for saving spaces
before Delete on SmtTable
for each row
declare
v_loghis SmtTable%rowtype;
v_tabVar varchar2(2000);
begin
v_loghis.empo := 'old' || :old.empo;
v_loghis.name := 'old' || :old.name
v_loghis.adress:= 'old || :old.adress';
here what i can use for inserting v_loghis into one column
for r in v_loghis
loop
v_tabVar := ':' || r.vloghis ;
end loop
insert into TabHis(col1) values(v_tabVar);
end;
was error v_loghis not cursor..

for saving spaces
You must be kidding. You won't save any space, but create a nightmare for future revisions of data you store.
Because, your next question will be:
I have a long string that looks like this: "old.7369:old.SCOTT:old.Traffalgar Square 23/A, London". How do I join this "7369" EMPO column value to EMPLOYEES table?
Shortly: don't do that. If you're performing some kind of an audit, then store each column separately.
Don't forget the timestamp column (so that you'd know when something happened) and operation (insert/update/delete) that caused that row to be inserted into the audit table.

Related

PL/SQL Trigger: Facing bad bind variable error

I have tried everything but nothing can solve this.PLS-00049: bad bind variable 'NEW.BLOODBANKID' keep appearing. Please help me.
CREATE OR REPLACE TRIGGER count_stock
AFTER INSERT OR UPDATE ON blood
FOR EACH ROW
DECLARE
v_countblood NUMBER;
BEGIN
SELECT COUNT(bloodid) INTO v_countblood
FROM blood
WHERE bloodbankid = :NEW.bloodbankid
AND bloodtype = :NEW.bloodtype;
IF v_countblood < 0 THEN
RAISE_APPLICATION_ERROR(-20201,'There is no blood');
END IF;
END;
The error indicates that there is no column "BLOODBANKID" in the table "BLOOD".
Either it doesn't exist at all in the table or you used double quotes on the column in your CREATE TABLE statement, thus making the column case sensitive.
If for instance your CREATE TABLE statement looks like this:
create table blood("bloodbankid" number(18), ...);
Then there is a column "bloodbankid" in the table, but no column "BLOODBANKID".
When we access a row without quotes as in
select BLOODBANKID from blood where BloodBankId = 123;
then Oracle converts this internally into "BLOODBANKID". As the same applies to CREATE TABLE, the column names usually match. If you used quotes in the CREATE TABLE statement, however, then you must use the same upper/lower case with quotes in every statement:
select "bloodbankid" from blood where "bloodbankid" = 123;
So, if this is the case, I'd recommend you re-create the table with case insensitive columns.
(Apart from this your trigger doesn't make sense anyway, as has already been mentioned in the request comments.)

How to duplicate rows with a column change for a list of tables?

Given the following example:
BEGIN
FOR r IN (
SELECT * FROM table_one WHERE change_id = 0
) LOOP
r.change_id := -1;
INSERT INTO table_one VALUES r;
END LOOP;
END;
This inserts new rows to table_one with the exact same content, except the intended change on column change_id to the value -1. I don't have to specify the columns inside of the script as I have to in an INSERT INTO table_one (change_id, ...) SELECT -1, ... FROM table_one WHERE change_id=0;
It works perfectly fine. But how to modify this script to work with a list of tables? The internal structure of those tables are different, but all of them have the necessary column change_id.
Of course the easiest solution would be to copy and paste this snippet x-times and replace the fix table name inside. But is there an option to work with a list of tables in an array?
My approach was like this:
DECLARE
TYPE tablenamearray IS VARRAY(30) OF VARCHAR2(30);
tablenames tablenamearray;
BEGIN
tablenames := tablenamearray('TABLE_ONE', 'TABLE_TWO', 'TABLE_THREE'); -- up to table 30...
FOR i IN tablenames.first..tablenames.last LOOP
/* Found no option to use tablenames(i) here with dynamic SQL */
END LOOP;
END;
Note: There is no technical primary key like an id with a sequence behind. The primary key is build by three columns incl. the change_id column.
You cannot create a SQL statement where the statement is not known at parse time. So, you cannot have a variable as a table name. What you're looking for is Dynamic SQL, which is a fairly complicated topic, but basically you're going to wind up building a SQL statement with DBMS_SQL or running a statement as a string with EXECUTE IMMEDIATE.

Inserting Row Number based on existing value in the table

I have a requirement that I need to insert row number in a table based on value already present in the table. For example, the max row_nbr record in the current table is something like this:
+----------+----------+------------+---------+
| FST_NAME | LST_NAME | STATE_CODE | ROW_NBR |
+----------+----------+------------+---------+
| John | Doe | 13 | 123 |
+----------+----------+------------+---------+
Now, I need to insert more records, with given FST_NAME and LST_NAME values. ROW_NBR needs to be generated while inserting the data into table with values auto-incrementing from 123.
I can't use a sequence, as my loading process is not the only process that inserts data into this table. And I can't use a cursor as well, as due to high volume of data the TEMP space gets filled up quickly. And I'm inserting data as given below:
insert into final_table
( fst_name,lst_name,state_code)
(select * from staging_table
where state_code=13);
Any ideas how to implement this?
It sounds like other processes are finding the current maximum row_nbr value and incrementing it as they do single-row inserts in a cursor loop.
You could do something functionally similar, either finding the maximum in advance and incrementing it (if you're already running this in a PL/SQL block):
insert into final_table (fst_name, lst_name, state_code, row_nbr)
select st.*, variable_holding_maximum + rownum
from staging_table st
where st.state_code=13;
or by querying the table as part of the query, which doesn't need PL/SQL:
insert into final_table (fst_name, lst_name, state_code, row_nbr)
select st.*, (select max(row_nbr) from final_table) + rownum
from staging_table st
where st.state_code=13;
db<>fiddle
But this isn't a good solution because it doesn't prevent clashes from different processes and sessions trying to insert at the same time; but neither would the cursor loop approach, unless it is catching unique constraint errors and re-attempting with a new value, perhaps.
It would be better to use a sequence, which would be an auto-increment column but you said you can't change the table structure; and you need to let the other processes continue to work without modification. You can still do that with a sequence and trigger approach, having the trigger always set the row_nbr value form the sequence, regardless of whether the insert statement supplied a value.
If you create a sequence that starts from the current maximum, with something like:
create sequence final_seq start with <current max + 1>
or without manually finding it:
declare
start_with pls_integer;
begin
select nvl(max(row_nbr), 0) + 1 into start_with from final_table;
execute immediate 'create sequence final_seq start with ' || start_with;
end;
/
then your trigger could just be:
create trigger final_trig
before insert on final_table
for each row
begin
:new.row_nbr := final_seq.nextval;
end;
/
Then your insert ... select statement doesn't need to supply or even think about the row_nbr value, so you can leave it as you have it now (except I'd avoid select * even in that construct, and list the staging table columns explicitly); and any existing inserts that do supply the row_nbr don't need to be modified and the value they supply will just be overwritten from the sequence.
db<>fiddle showing inserts with and withouth row_nbr specified.

Auditing a table with many columns without Fine Grained Auditing

I have to create a trigger for a table with many columns and I want to now if is any possibility to avoid using the name of the column after :new and :old. Instead of specifically use the column name I want to use the element from collection with column names of target table (the table on which the trigger is set).
The line 25 is that with the binding error:
DBMS_OUTPUT.PUT_LINE('Updating customer id'||col_name(i)||to_char(:new.col_name(i)));
Bellow you can see my trigger:
CREATE OR REPLACE TRIGGER TEST_TRG BEFORE
INSERT OR
UPDATE ON ITEMS REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW DECLARE TYPE col_list IS TABLE OF VARCHAR2(60);
col_name col_list := col_list();
total INTEGER;
counter INTEGER :=0;
BEGIN
SELECT COUNT(*)
INTO total
FROM user_tab_columns
WHERE table_name = 'ITEMS';
FOR rec IN
(SELECT column_name FROM user_tab_columns WHERE table_name = 'ITEMS'
)
LOOP
col_name.extend;
counter :=counter+1;
col_name(counter) := rec.column_name;
dbms_output.put_line(col_name(counter));
END LOOP;
dbms_output.put_line(TO_CHAR(total));
FOR i IN 1 .. col_name.count
LOOP
IF UPDATING(col_name(i)) THEN
DBMS_OUTPUT.PUT_LINE('Updating customer id'||col_name(i)||to_char(:new.col_name(i)));
END IF;
END LOOP;
END;
Sincerely,
After digging more I have found that is not possible to dynamically reference the :new.column_name or :old.column_name values in a trigger. Due to this I will use my code only to INSERT (it does not have an old value :-() and I will do some code in java to generate UPDATE statements.
I must refine my previous answer based on what has been said by Justin Cave and also my findings. We can create a dynamic list of values triggered by INSERTING and UPDATING, based on referencing clause (old and new). For example I have created 2 collections of type nested table with varchars. One collection will contain all column tabs, as strings, that I will use for auditing and another collection will contains values for that columns with binding reference (ex. :new.). After INSERTING predicate I have created a index by collection (an associative array) of strings with ID taken from list of strings with column tab name and the value taken from the list of values for that columns referenced by new. Due to the index by collection you have a full working dynamic list at your disposal. Good luck :-)

traverse among the columns of oracle tables

This trigger propose to track the old and new values on customer table.
It only inserts a new row for every changed value columns.
Imagine there are twentyfive columns and I got to write the same thing for 25 times.
Are there any way to do this in a loop or any better way to keep more generic. Imagine If any new columnd added to customer table, with the way you tell me there will be no need to alter the trigger.
CREATE OR REPLACE TRIGGER KRD_CUSTOMER_UPD_DEL_TRG
BEFORE UPDATE OR DELETE ON KRD_CUSTOMER
FOR EACH ROW
DECLARE
V_ISLEMTIPI VARCHAR2(1);
BEGIN
IF UPDATING THEN
if :OLD.CUSTOMERNAME <> :NEW.CUSTOMERNAME then
krd_ins_customerTable(p_OldCustomerName => :OLD.CUSTOMERNAME,
p_NewCustomerName => :NEW.CUSTOMERNAME);
end if;
end if;
end;
The column names are available in the view: user_tab_columns.
Something that may work:
Loop through the column names in this view and construct a string containing the statement that must be executed for that particular column.
l_statement := 'begin if :OLD.<columname> <> :NEW.<columname> then
krd_ins_customerTable(p_Old<columname> => :OLD.<columname>,
p_New<columname> => :NEW.<columname>); end;'
execute immediate l_statement;
As Ollie already commented: To check for non equality also check the null values.
If (a is null and b is not null)
or (a is not null and b is null)
or (a is not null and b is not null and a!=b)

Resources