plsql Trigger to generate and insert values - sql

I am trying to create a trigger to automatic generate and insert values with year and month (YYYYMM) into a table as inserts are made into another table.
Example:
As inserts are made into table 'original_table'
create table original_table
(opt_value char(2),
low_value varchar2(24),
high_value varchar2(24));
create table new_values
(id_values varchar2(24),
yr_month number(6));
Insert into original_table(opt_value,low_value,high_value) values ('EQ', '1111111111', '1111111111');
Insert into original_table(opt_value,low_value,high_value) values ('BT', '2222222000', '2222222999');
Insert into original_table(opt_value,low_value,high_value) values ('BT', '3333333350', '3333333399');
original_table
opt_value
low_value
high_value
EQ
1111111111
1111111111
BT
2222222000
2222222999
BT
3333333350
3333333399
Obs: Where EQ stands for 'equal' and BT 'between'. When 'EQ' just need to insert one of the values low or high don't matter, when 'BT' need to generate all the numbers between the two values and insert then into 'new_values' table.
table 'new_values' should get:
new_values
id_values
yr_month
1111111111
202111
2222222000
202111
2222222001
202111
2222222002
202111
...
...
2222222999
202111
3333333350
202111
3333333351
202111
...
...
3333333399
202111
I create a trigger which works, but i find it a little slow, and i don't like to use BEFORE INSERT statement, but can't use AFTER INSERT without getting mutating table error.
create or replace TRIGGER TRG_NAME
BEFORE INSERT
ON original_table
FOR EACH ROW
BEGIN
IF :NEW.opt_value = 'BT' THEN
INSERT INTO new_values (id_values, yr_month)
with tab123 (h_value, l_value, y_month)
as (select :NEW.high_value, cast(:NEW.low_value as number) , to_char(trunc(sysdate), 'YYYYMM')
from original_table
union all
select h_value, l_value +1, y_month
from tab123
where l_value < h_value)
select distinct l_value, y_month
from tab123;
ELSIF :NEW.opt_value = 'EQ' THEN
INSERT INTO new_values (id_values, yr_month) values
( :NEW.high_value, to_char(add_months(trunc(sysdate), -1), 'YYYYMM'));
END IF;
END;
Any tips in how to improve this code will be much appreciated.

You get a mutating table error, when you read from the table that is changing.
It's weird that you are getting it in an AFTER INSERT trigger and not with a BEFORE INSERT trigger. I would have assumed them to both result in the same error, because in both scenarios you read from the triggering table in your trigger. Well, this may have to do with only inserting one row. If you insert more rows at a time, you may get the error with both trigger variants.
In your case reading all the rows from the original_table in your trigger only produces duplicates anyway, that you fend off with DISTINCT. Instead, don't read from the table. It is not necessary. Read from DUAL instead, to get one row with the desired values:
with tab123 (h_value, l_value, y_month) as
(
select :new.high_value, cast(:new.low_value as number), to_char(sysdate, 'yyyymm')
from dual
union all
select h_value, l_value + 1, y_month
from tab123
where l_value < h_value
)
select l_value, y_month
from tab123;
This will get you rid of the mutating table error and also speed up the trigger.

Related

How to insert Oracle Collection data into CLOB Column of a table

I have records in a collection variable. O want to insert all records into CLOB Column of table.
set serveroutput on;
declare
type ROW_DATA is table of varchar2(256) ;
ROW_D ROW_DATA;
begin
with DIFF_TAB_DATA as
(
select SOME_COLUMN from SOME_TABLE1
union all
select SOME_COLUMN from SOME_TABLE2
union all
select SOME_COLUMN from SOME_TABLE3
union all
select SOME_COLUMN from SOME_TABLE4
union all
select SOME_COLUMN from SOME_TABLE5
)
select SOME_COLUMN bulk collect into ROW_D from DIFF_TAB_DATA;
insert into CLOB_TAB values(ROW_D);
end;
But I am getting the error that local collection variable can not be used in insert statement.
Yes, that's a mismatch. The local collection types can't be replaced with the data type of the current value, those are totally unrelated. Rather call the collection along with an indexed set to extract the values stored in them. In order to perform this just replace
INSERT INTO clob_tab VALUES(row_d);
with
FORALL indx IN 1 .. row_d.COUNT
INSERT INTO clob_tab VALUES(row_d(indx));

How to SQL Query Large List of Tables using variable for table names?

I have a question about running a Oracle DB query on multiple tables. Is there a way to make the table names variables to be iterated as opposed to having to state each table name?
Background Example
There are a large number of tables (ex. TABLE_1...TABLE_100).
Each of these tables are listed in the NAME column of another table (ex. TABLE_LIST) listing an even larger number of tables along with TYPE (ex. "Account")
Each of these tables has columnn VALUE a boolean column, ACTIVE.
Requirements
Query the TABLE_LIST by TYPE = 'Account'
For Each table found, query that table for all records where column ACTIVE = 'N'
Results show table NAME and VALUE from each table row where ACTIVE = 'N'.
Any tips would be appreciated.
There is a low tech and a high tech way. I'll put them in separate answers so that people can vote for them. This is the high tech version.
Set up: Same as in low tech version.
CREATE TYPE my_row AS OBJECT (name VARCHAR2(128), value NUMBER)
/
CREATE TYPE my_tab AS TABLE OF my_row
/
CREATE OR REPLACE FUNCTION my_fun RETURN my_tab PIPELINED IS
rec my_row := my_row(null, null);
cur SYS_REFCURSOR;
BEGIN
FOR t IN (SELECT name FROM table_list WHERE table_type='Account') LOOP
rec.name := dbms_assert.sql_object_name(t.name);
OPEN cur FOR 'SELECT value FROM '||t.name||' WHERE active=''N''';
LOOP
FETCH cur INTO rec.value;
EXIT WHEN cur%NOTFOUND;
PIPE ROW(rec);
END LOOP;
CLOSE cur;
END LOOP;
END my_fun;
/
SELECT * FROM TABLE(my_fun);
NAME VALUE
TABLE_1 1
TABLE_3 3
There is a low tech and a high tech way. I'll put them in separate answers so that people can vote for them. This is the low tech version.
Set up:
CREATE TABLE table_1 (value NUMBER, active VARCHAR2(1) CHECK(active IN ('Y','N')));
CREATE TABLE table_2 (value NUMBER, active VARCHAR2(1) CHECK(active IN ('Y','N')));
CREATE TABLE table_3 (value NUMBER, active VARCHAR2(1) CHECK(active IN ('Y','N')));
INSERT INTO table_1 VALUES (1, 'N');
INSERT INTO table_1 VALUES (2, 'Y');
INSERT INTO table_3 VALUES (3, 'N');
INSERT INTO table_3 VALUES (4, 'Y');
CREATE TABLE table_list (name VARCHAR2(128 BYTE) NOT NULL, table_type VARCHAR2(10));
INSERT INTO table_list (name, table_type) VALUES ('TABLE_1', 'Account');
INSERT INTO table_list (name, table_type) VALUES ('TABLE_2', 'Something');
INSERT INTO table_list (name, table_type) VALUES ('TABLE_3', 'Account');
The quick and easy way is to use a query to generate another query. I do that quite often, especially for one off jobs:
SELECT 'SELECT '''||name||''' as name, value FROM '||name||
' WHERE active=''N'' UNION ALL' as sql
FROM table_list
WHERE table_type='Account';
SELECT 'TABLE_1' as name, value FROM TABLE_1 WHERE active='N' UNION ALL
SELECT 'TABLE_3' as name, value FROM TABLE_3 WHERE active='N' UNION ALL
You'll have to remove the last UNION ALL and execute the rest of the query. The result is
NAME VALUE
TABLE_1 1
TABLE_3 3

Insert using IF/ELSE statements

I am sorry if my question is not clear or my query is not sufficient to help. I have a procedure that has multiple if/else statement. My goal is to insert one row if that if statements meets the criteria else go further. Something like this:
create or replace procedure abc.xyz
( i_name varchar2
,number number,
sections varchar2)
...
max_date date;
min_date date;
...
if(sum=0)
insert into abc_table
(id,name,number,sections,description,date,amount,price,source,latest_date)
select user_seq.nextval,name,number,max_date,amount,0
,'xyz',trunc(sysdate))
from abc_table x
where x.name=i_name
and x.number=i_name
and x.section=i_section;
elseif (sum>0)
insert into abc_table
(id,name,number,sections,description,date,amount,price,source,latest_date)
select user_seq.nextval,name,number,max_date,amount,0
,'xyz',trunc(sysdate))
from abc_table x
where x.name=i_name
and x.number=i_name;
and x.section=i_section;
when i run my procedure, to insert the calculated value , the values are correct but so many rows were inserted. How can I prevent from multiple insert and make only one row insert ?
You are doing it wrong.
According to the Oracle documentation, The syntax for the Oracle INSERT statement when inserting a single record using the VALUES keyword is:
INSERT INTO table
(column1, column2, ... column_n )
VALUES
(expression1, expression2, ... expression_n );
But the syntax for the Oracle INSERT statement when inserting multiple records using a SELECT statement is:
INSERT INTO table
(column1, column2, ... column_n )
SELECT expression1, expression2, ... expression_n
FROM source_table
[WHERE conditions];
reference: https://www.techonthenet.com/oracle/insert.php
from the link i shared:
If you don't want to insert duplicate:
INSERT INTO clients
(client_id, client_name, client_type)
SELECT 10345, 'IBM', 'advertising'
FROM dual
WHERE NOT EXISTS (SELECT *
FROM clients
WHERE clients.client_id = 10345);

Insert 1000 rows in single sql statment or procedure

How should i write a single sql statement or a stored procedure,
To insert 1000 values in 1000 rows and same column with each column having different values (among those 1000)
Here is the query i wrote,
INSERT INTO a_b values
(
(SELECT max(a_b_id) + 1 from a_b),
1111,
(SELECT s_id FROM a_b WHERE s_id in ('0','1','2','3','4')),
0,
1,
sysdate,
sysdate,
0,
1,
null
);
like say, i have 1000 s_id's i want select them one by one and insert them in one particular column, each time creating a new row.
EX, in first row s_id should be 0 then in second row it should be 1 like that goes on till thousand, attached an image of sample database i am working with.
You can use connect by for this:
INSERT INTO a_b (s_id, col2, col3, ....)
select level, --<< this value will change for every row
1111,
sysdate,
... more columns ...
from dual
connect by level <= 1000;
you can use cross apply to get 1000 rows along with 1000 other columns to insert 1000 rows as below:
insert into a_b (column names...)
select (max(a_b_id) over()) +1 as MaxId, s_id from a_b a cross apply (select 0, 1,SYSDATETIME, SYSDATETIME, 0, 1, null) b where a.s_id('111','222')--condition
The below is a syntax error. You will never get something like that to work.
create table fff
( id int not null
);
insert fff values (select 1,7777,select 3, select 3);
So you need to break it up into chunks
DROP PROCEDURE IF EXISTS uspRunMe;
DELIMITER $$
CREATE PROCEDURE uspRunMe()
BEGIN
insert into a_b select max(a_b_id) + 1 from a_b;
insert into a_b values (1111);
insert into a_b SELECT s_id FROM a_b WHERE s_id in ('0','1','2','3','4');
insert into a_b values (0,1);
insert into a_b select sysdate,sysdate;
insert into a_b values (0,1,null);
END;$$
DELIMITER ;
Test it:
call uspRunMe();
The above is for MySQL. You have a few db engines tagged here.

PL/SQL trigger to insert next value

I created a trigger which works like when I update/insert a row in one table, an insert of a row will a done in another table which contains a primary key.
Now when I insert a row in the first table I want the trigger to check the last value of primary key of another table and if that is null or '-' then I've to insert 1 into that primary key column so as to insert the remaining values.
I've written the code as follows:
create or replace trigger "T1"
AFTER
insert or update on "buses"
for each row
begin
-- Here I want to check the V_id on vehicles table, if that is null or '-' then insert V_id as 1 along with the below insert statement.
if :NEW."b_key" is not null then
INSERT INTO vehicles (b_KEY,B_NAME,ADDRESS_1,CITY,STATE,ZIP,PHONE,WEBSITE) VALUES (:new.b_KEY,:new.b_NAME,:new.ADDRESS_1,:new.CITY,:new.STATE,:new.ZIP,:new.PHONE,:new.WEBSITE);
end if;
end;
How to find the last b_id in the vehicles table, so that if that value is null or '-' insert b_id as 1, followed by the above insert statement in the same row.
By adding another trigger we can do that as follows:
create or replace TRIGGER "B_VEHICLES"
before insert on "buses"
for each row
declare b_number number;
begin
select max(B_ID) into b_number from Vehicles;
if :OLD."B_ID" is null and b_number is null then
select 1 into :new."B_ID" from dual;
else select b_number + 1 into :new."B_ID" from dual;
end if;
end;​