How to insert 1000 rows into an Oracle database - oracle

I have this stored procedure:
create or replace procedure InsertProduct
(product_name VARCHAR2,
product_price int,
product_description varchar2)
as
begin
insert into PRODUCT (name, price, description, create_date)
values (product_name, product_price, product_description, sysdate);
end;
I used it to insert 1000 rows at time:
begin
for insert_loop in 1..1000 loop
InsertProduct(dbms_random.string('x', 10), DBMS_RANDOM.value(low => 1, high => 100000) , dbms_random.string('x', 10));
end loop;
commit;
end;
delete from PRODUCT;
but this loop adds random records, is it possible to do so to add real product records, if yes, how to do it?

is it possible to do so to add real product records
Yes, just call your procedure 1000 times with the real data.
For example:
begin
InsertProduct('Apple', 45, 'A red fruit');
InsertProduct('Banana', 33, 'A yellow fruit');
InsertProduct('Cucumber', 70, 'A green vegetable');
InsertProduct('Date', 5, 'A brown fruit');
commit;
end;

Have you looked into using FORALL? If you have the ability to create a collection of records for the data that you want to insert, you can insert them all using one statement similar to the example below.
Using FORALL will also make your code perform faster since it is only 1 INSERT statement with all the records vs 1000 individual INSERT statemements.
DECLARE
TYPE product_t IS TABLE OF product%ROWTYPE;
l_products product_t := product_t();
BEGIN
l_products.extend(3); --number of items we'll be creating
l_products (1).name := 'name1';
l_products (1).price := 1;
l_products (1).description := 'description1';
l_products (2).name := 'name2';
l_products (2).price := 2;
l_products (2).description := 'description2';
l_products (3).name := 'name3';
l_products (3).price := 3;
l_products (3).description := 'description3';
FORALL i IN 1 .. l_products.COUNT
INSERT INTO product (name,
price,
description,
create_date)
VALUES (l_products (i).name,
l_products (i).price,
l_products (i).description,
SYSDATE);
END;
If you want to see how sample data can be created, the example below will create 1500 rows of sample data. Again this will perform faster than the code you provided because it is 1 INSERT vs 1500 INSERTs that it would take using your code.
DECLARE
TYPE product_t IS TABLE OF product%ROWTYPE;
l_products product_t := product_t ();
BEGIN
FOR i IN 1 .. 1500
LOOP
l_products.EXTEND;
l_products (i).name := DBMS_RANDOM.string ('x', 10);
l_products (i).price := DBMS_RANDOM.VALUE (low => 1, high => 100000);
l_products (i).description := DBMS_RANDOM.string ('x', 10);
END LOOP;
FORALL i IN 1 .. l_products.COUNT
INSERT INTO product (name,
price,
description,
create_date)
VALUES (l_products (i).name,
l_products (i).price,
l_products (i).description,
SYSDATE);
END;

Related

Creating a complex ID using a trigger

In many of our applications we use an identity column to generate a unique number, such as a customer_id.
Our internal auditors feel this is a possible breach of security and going forward we want to use something a bit more complex.
I found a function below base34 that I want to pass a concatenation of SYS_Guid, part of a TIMESTAMP and a sequence number to create a more complex ID.
Below is my test CASE. Is there a way I can use the base34 function in a before INSERT trigger with the above concatenation without changing the base34 function to achieve this task.
For example, let's say I have the following table.
CREATE TABLE CUSTOMERS (
customer_id VARCHAR2 (20),
first_name VARCHAR2 (20),
last_name VARCHAR2 (20));
And I want the trigger to populate customer_id
Thanks in advance for your time and expertise.
create table t ( pk number);
create sequence seq start with 1000000 minvalue 1000000 maxvalue 9999999 cycle;
begin
for i in 1 .. 10 loop
insert into t values ( to_number(trunc(dbms_random.value(1000,9999))|| to_char(systimestamp,'FFSS')||
seq.nextval));
end loop;
end;
/
create or replace function base34(p_num number) return varchar2 is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := p_num;
l_str varchar2(38);
begin
loop
l_str := substr(l_dig,mod(l_num,34)+1,1) || l_str ;
l_num := trunc(l_num/34);
exit when l_num = 0;
end loop;
return l_str;
end;
/
create or replace function dec34(p_str varchar2) return number is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := 0;
begin
for i in 1 .. length(p_str) loop
l_num := l_num * 34 + instr(l_dig,upper(substr(p_str,i,1)))-1;
end loop;
return l_num;
end;
/
select base34(pk) from t where rownum <= 10;
select to_char(pk) from t where rownum = 1
union all
select base34(pk) from t where rownum = 1
union all
select to_char(dec34(base34(pk))) from t where rownum = 1;
CREATE TABLE CUSTOMERS (
customer_id VARCHAR2 (20),
first_name VARCHAR2 (20),
last_name VARCHAR2 (20));
create sequence seq start with 1000000 minvalue 1000000 maxvalue 9999999 cycle;
create or replace function base34(p_num number) return varchar2 is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := p_num;
l_str varchar2(38);
begin
loop
l_str := substr(l_dig,mod(l_num,34)+1,1) || l_str ;
l_num := trunc(l_num/34);
exit when l_num = 0;
end loop;
return l_str;
end;
CREATE OR REPLACE TRIGGER customer_trg
BEFORE INSERT
ON customers
FOR EACH ROW
BEGIN
:NEW.customer_id := base34(to_number(trunc(dbms_random.value(1000,9999))|| to_char(systimestamp,'FFSS')||
seq.nextval));
END;
begin
for i in 1 .. 100 loop
INSERT into customers (first_name, last_name) VALUES ('John', 'Doe');
end loop;
end;
/

PLSQL looping through JSON object

Basically I have a json that looks like this [{"group":"groupa","status":"active"},{"group":"groupb","status":"inactive"}] and I want to loop through and extract the group only and save them in a variable in order to loop and compare the groups to a certain variable.
for example
group := 'groupc'
while counter < jsonGroup.count
loop
if jsonGroup(counter) := group then ....
is there any way to save the group into jsonGroup as an array or table?
thank you
From Oracle 12, you can use JSON PL/SQL object types to iterate over the JSON array and to extract the value of the group attribute of the objects:
DECLARE
value VARCHAR2(4000) := '[{"group":"groupa","status":"active"},{"group":"groupb","status":"inactive"}]';
ja JSON_ARRAY_T := JSON_ARRAY_T.PARSE(value);
je JSON_ELEMENT_T;
grp VARCHAR2(20);
i PLS_INTEGER := 0;
BEGIN
LOOP
je := ja.GET(i);
EXIT WHEN je IS NULL;
grp := TREAT(je AS JSON_OBJECT_T).get_string('group');
DBMS_OUTPUT.PUT_LINE(grp);
i := i + 1;
END LOOP;
END;
/
Which outputs:
groupa
groupb
The values can be stored into an array using the JSON_ARRAY_T functionality as MT0 described or using JSON_TABLE which might be better if your JSON is already stored in a table.
Below is an example of how to use both methods to store the groups into an array.
DECLARE
l_json_text VARCHAR2 (100)
:= '[{"group":"groupa","status":"active"},{"group":"groupb","status":"inactive"}]';
TYPE tab_t IS TABLE OF VARCHAR2 (100);
l_table tab_t := tab_t ();
l_array json_array_t;
PROCEDURE print_tab
IS
BEGIN
FOR i IN 1 .. l_table.COUNT
LOOP
DBMS_OUTPUT.put_line (l_table (i));
END LOOP;
END;
BEGIN
l_array := json_array_t (l_json_text);
l_table.EXTEND (l_array.get_size);
FOR i IN 1 .. l_array.get_size
LOOP
l_table (i) := TREAT (l_array.get (i - 1) AS json_object_t).get_string ('group');
END LOOP;
DBMS_OUTPUT.put_line ('After JSON_ARRAY_T method');
print_tab;
l_table.delete;
DBMS_OUTPUT.put_line ('After delete');
print_tab;
SELECT grp
BULK COLLECT INTO l_table
FROM JSON_TABLE (l_json_text, '$[*]' COLUMNS grp PATH '$.group');
DBMS_OUTPUT.put_line ('After JSON_TABLE method');
print_tab;
END;
/

How to Log Alter Column DDL Operations

I need to create a database trigger which will record each alter (Add Column,Modify Column,Drop Column) statements in a specific table using Oracle's schema trigger. How to get it ?
I tried the code below so far :
TRIGGER after_ddl_creation
after CREATE ON SCHEMA
DECLARE
V CLOB;
BEGIN
FOR REC IN(SELECT TEXT FROM user_source WHERE NAME=SYS.DICTIONARY_OBJ_NAME)
LOOP
V:=V||REC.TEXT;
END LOOP ;
INSERT INTO myAudit VALUES
(SYS.DICTIONARY_OBJ_NAME,SYS.DICTIONARY_OBJ_TYPE,SYSDATE,USER,NULL,NULL,V);
END;
You can use such a database trigger :
create or replace trigger after_ddl_creation after ddl on schema
declare
v_oty varchar2(75) := ora_dict_obj_type;
v_don varchar2(75) := ora_dict_obj_name;
v_evt varchar2(75) := ora_sysevent;
v_olu varchar2(75) := nvl(ora_login_user,'Undefined Schema');
v_sql ora_name_list_t;
v_stm clob;
v_sct owa.vc_arr;
n pls_integer;
n_max pls_integer := 10000;
--> can log upto ten-thousand rows of "text" value, within "stmt" column,
--> which can be accessed by using (`[user|all|dba]_source`) views.
begin
v_sct(1) := 'SESSIONID';
v_sct(2) := 'IP_ADDRESS';
v_sct(3) := 'TERMINAL';
v_sct(4) := 'OS_USER';
v_sct(5) := 'AUTHENTICATION_TYPE';
v_sct(6) := 'CLIENT_INFO';
v_sct(7) := 'MODULE';
for i in 1..7
loop
v_sct(i) := sys_context('USERENV',v_sct(i));
end loop;
select decode(v_sct(1),0,null,v_sct(1)),
decode(upper(v_sct(3)),'UNKNOWN',null,v_sct(3))
into v_sct(1),v_sct(3) from dual;
n := ora_sql_txt( v_sql );
if n > n_max then
n := n_max;
end if;
for i in 1..n
loop
v_stm := v_stm || v_sql(i);
end loop;
if ( evt = 'ALTER' and oty = 'TABLE'
and regexp_like(v_stm,'Add|Modify|Drop','i') ) then
insert into myAudit(ts,usr,evnt,stmt,sessionid,ip,terminal,os_user,auth_type,
object_type,object_name,client_info,module_info)
values(sysdate,v_olu,v_evt,v_stm,v_sct(1),v_sct(2),v_sct(3),v_sct(4),v_sct(5),
v_oty,v_don,v_sct(6),v_sct(7));
end if;
end;
by recreating myAudit table due to the above INSERT statement.

Take Oracle Type Array and insert contents into an Oracle table

I have a variable l_rec of type wwv_flow_global.vc_arr2 within my package procedure, where:
type vc_arr2 is table of varchar2(32767) index by binary_integer;
Within l_rec, I have populated a number of records.
Within my debug statement, I can access the records using the following query:
FOR i IN 1..l_rec.COUNT
LOOP
insert into msg_log(msg)
values
('Record info: Index: ' || i || ' - Value: ' || l_rec(i));
END LOOP;
FYI, I actually also have an outer loop that repeats the below info but with different data, i.e. a loop within a loop.
Sample dataset looks like:
Record info: Index: 1 - Value: AA
Record info: Index: 2 - Value: BB
Record info: Index: 3 - Value: CC
Record info: Index: 4 - Value: DD
Record info: Index: 5 - Value: EE
Record info: Index: 1 - Value: AAA
Record info: Index: 2 - Value: BBB
Record info: Index: 3 - Value: CCC
Record info: Index: 4 - Value: DDD
Record info: Index: 5 - Value: EEE
etc....
Based on the above, I have created a table called message_log that has the following columns:
SEQ_ID NUMBER,
C001 VARCHAR2(4000),
C002 VARCHAR2(4000),
C003 VARCHAR2(4000),
C004 VARCHAR2(4000),
C005 VARCHAR2(4000)
My question is, how can I take my l_rec array of type wwv_flow_global.vc_arr2 and insert the whole contents into my message_log Oracle table?
Please note that SEQ_ID here will be a counter of my outer loop so I would expect to see message log table data as follows:
1,AA,BB,CC,DD,EE
2,AAA,BBB,CCC,DDD,EEE
If you work with apex_t_varchar2, you can
select * from table(apex_string.split('1,2,3',','));
or
declare
l_table apex_t_varchar2;
begin
apex_string.push(l_table, 'a');
apex_string.push(l_table, 'b');
sys.dbms_output.put_line(apex_string.join(l_table, ','));
end;
/
Which I think would cover a few of your needs.
You have a few options.But direct insert is not possible.
create table msg_log(
seq_id number,
col1 varchar2(4000),
col2 varchar2(4000),
col3 varchar2(4000),
col4 varchar2(4000),
col5 varchar2(4000),
col6 varchar2(4000),
col7 varchar2(4000),
col8 varchar2(4000));
Preparing test table.
In static approach each value from your record is assigned to rowtype record.
In dynamic approach whole insert is generated.
declare
type vc_arr2 is table of varchar2(32767) index by binary_integer;
rec vc_arr2;
row_msg_log msg_log%rowtype ;
function populate(how_many number) return vc_arr2 is
tmp vc_arr2;
begin
for i in 1 .. how_many loop
tmp(i) := 'VALUE'||i;
end loop;
return tmp;
end;
function static_approach(id number , rec vc_arr2) return msg_log%rowtype
is
tmp msg_log%rowtype;
begin
tmp.seq_id := id;
if rec.exists(1) then
tmp.col1 := rec(1);
end if;
if rec.exists(2) then
tmp.col2 := rec(2);
end if;
if rec.exists(3) then
tmp.col3 := rec(3);
end if;
--etc.
return tmp;
end;
procedure dynamic_insert(id number , rec vc_arr2) is
v_sql varchar2(4000);
function generate_stament return varchar2 is
idx number;
column_list varchar2(4000);
value_list varchar2(4000);
begin
column_list := '(seq_id';
value_list := '('||id;
idx := rec.first;
while (idx is not null)
loop
column_list := column_list||' ,col'||idx;
value_list := value_list||' ,'''||rec(idx)||'''';
idx := rec.next(idx);
end loop;
column_list := column_list||') ';
value_list := value_list||') ';
return 'insert into msg_log'||column_list||'values'||value_list;
end;
begin
v_sql := generate_stament;
execute immediate v_sql;
end;
begin
row_msg_log := static_approach(1,populate(3));
insert into msg_log values row_msg_log;
row_msg_log := static_approach(2,populate(4));
insert into msg_log values row_msg_log;
dynamic_insert(3,populate(8));
dynamic_insert(4,populate(1));
-- with a not dens array
rec := populate(1);
rec(5) := 'blblb';
rec(8) := 'fofofo';
dynamic_insert(4,rec);
end;

pl/sql procedure error issue

I'm trying to make a procedure in pl/sql but I can't. I have 2 tables, one has 100 names in it and the other has 100 surnames. I want to make a procedure that takes a random name and surname, and then I want to introduce that data into another table, but I get an error and I'm new to pl/sql and don't know what to do. Here is my code.
DROP TABLE grade
/
CREATE TABLE grade (grade CHAR(2))
/
INSERT INTO grade VALUES ('A1');
INSERT INTO grade VALUES ('A2');
INSERT INTO grade VALUES ('A3');
INSERT INTO grade VALUES ('A4');
INSERT INTO grade VALUES ('A5');
INSERT INTO grade VALUES ('A6');
INSERT INTO grade VALUES ('A7');
INSERT INTO grade VALUES ('B1');
INSERT INTO grade VALUES ('B2');
INSERT INTO grade VALUES ('B3');
INSERT INTO grade VALUES ('B4');
INSERT INTO grade VALUES ('B5');
INSERT INTO grade VALUES ('B6');
INSERT INTO grade VALUES ('B7');
commit;
set serveroutput on
DECLARE
v_contor INTEGER := 1;
v_nr NUMBER(10);
v_name VARCHAR2(20);
v_surname VARCHAR2(20);
v_year NUMBER(2);
v_grade VARCHAR2(20);
v_scholarship NUMBER(4);
v_nota NUMBER(2);
PROCEDURE insert_data IS
cursor c1 is
select name from(select name from name
order by dbms_ryeardom.value)
where rownum =1;
cursor c2 is
select surname from(select surname from surname
order by dbms_ryeardom.value)
where rownum =1;
cursor c3 is
select grade from(select grade from grade
order by dbms_ryeardom.value)
where rownum =1;
BEGIN
v_nr := 124;
WHILE v_contor <= 2000 LOOP
open c1;
open c2;
open c3;
LOOP
fetch c1 into v_name;
exit when c1%notfound;
END loop;
LOOP
fetch c2 into v_surname;
exit when c2%notfound;
END loop;
LOOP
fetch c3 into v_grade;
exit when c3%notfound;
END loop;
v_year := round(dbms_ryeardom.value(1,3));
v_scholarship := round(dbms_ryeardom.value(250,450));
INSERT INTO studenti VALUES (v_nr, INITCAP(v_name), v_surname, v_year, v_grade, v_scholarship, NULL);
IF (v_year = 2) THEN
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 21, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 22, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 23, v_nota, NULL);
END if;
IF (v_year = 3) THEN
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 21, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 22, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 23, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 24, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 25, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 26, v_nota, NULL);
v_nota := round(dbms_ryeardom.value(4,10));
INSERT INTO note VALUES (v_nr, 29, v_nota, NULL);
END if;
v_nr := v_nr + 1;
v_contor := v_contor + 1;
close c1;
close c2;
close c3;
END loop;
END;
/
BEGIN
insert_data;
END;
/
You are declaring the procedure in a PL/SQL anonymous block and not in the SQL - this is not a problem but you are completing the declaration of the procedure and are not completing the declaration of the anonymous block.
An anonymous PL/SQL block has the format:
DECLARE
...
BEGIN
...
END;
/
The PROCEDURE declaration goes between the DECLARE and BEGIN clauses and has the form:
PROCEDURE name IS ... BEGIN ... END;
There is no trailing / at the end of the procedure declaration as ; terminates a PL/SQL statement and / is used to terminate the outer-most PL/SQL block (indicating that you are returning to the SQL scope - and if you were nesting PL/SQL blocks then you would not use the '/' terminator on the inner blocks).
If you remove the first / from your code then it should get rid of the end-of-file error.
You should also declare the variables used in the procedure within the procedure and not in the containing PL/SQL anonymous block (i.e. between the procedure's IS and BEGIN clauses and not between the outer blocks DECLARE and the PROCEDURE statements).
If you want to declare the procedure in the SQL scope (and not in an anonymous block) then you need to use:
CREATE [OR REPLACE] PROCEDURE name IS ... BEGIN ... END;
/
(The trailing / will be required in this instance as the statement will not be contained in a surrounding PL/SQL anonymous block and will be declared directly in the SQL scope so needs the / to indicate that the declaration of the procedure has concluded.)
You also have an issue that your cursors contain the order by dbms_ryeardom.value which appears to be a typo for order by dbms_random.value.
There may also be an issue with repeatedly opening and closing the same cursors.
First:
Create or replace procedure AS
not
procedure IS
Second:
U cannot declare
v_contor INTEGER := 1;
v_nr NUMBER(10);
v_name VARCHAR2(20);
v_surname VARCHAR2(20);
v_year NUMBER(2);
v_grade VARCHAR2(20);
v_scholarship NUMBER(4);
v_nota NUMBER(2);
and then creatin procedure. This declarations have to be inside procedure after 'AS'. I don't remember if in procedure u type lenght of value or not
f.e. v_r number; instead of v_r number(10);
try both ways:)
summa summarum - You procedure should start like this
create or replace PROCEDURE insert_data AS
v_grade VARCHAR2;
v_scholarship NUMBER;
v_nota NUMBER;
....
and about the end:
END;
/
BEGIN
insert_data;
END;
/
change to
END;
/
BEGIN
execute(insert_data);
END;
/

Resources