I would like to know if there are ways I could optimize n improve performance of this stored procedure. I'm fairly new at it & would appreciate your help
To summary , the stored procedure takes an array . Each array an entry of the form a:b:c . After extracting the required data, an entry is made in a table.
create or replace
PROCEDURE insert_sku_prom_assigments (
sku_assigment_array tp_string_array_table
)
IS
first_colon_pos NUMBER;
second_colon_pos NUMBER;
sku VARCHAR2 (40);
assign VARCHAR2 (20);
promotion VARCHAR2 (40);
l_count INTEGER;
BEGIN
FOR i IN sku_assigment_array.FIRST .. sku_assigment_array.LAST
LOOP
first_colon_pos := INSTR (sku_assigment_array (i), ':', 1, 1);
second_colon_pos := INSTR (sku_assigment_array (i), ':', 1, 2);
sku := SUBSTR (sku_assigment_array (i), 1, first_colon_pos - 1);
assign :=
SUBSTR (sku_assigment_array (i),
first_colon_pos + 1,
second_colon_pos - first_colon_pos - 1
);
promotion := SUBSTR (sku_assigment_array (i), second_colon_pos + 1);
IF sku IS NOT NULL AND assign IS NOT NULL AND promotion IS NOT NULL
THEN
SELECT COUNT (*)
INTO l_count
FROM mtep_sku_promotion_rel_unver
WHERE sku_id = sku
AND assignment = assign
AND promotion_id = promotion
AND ROWNUM = 1;
IF l_count < 1
THEN
INSERT INTO mtep_sku_promotion_rel_unver
(sku_id, assignment, promotion_id
)
VALUES (sku, assign, promotion
);
END IF;
END IF;
l_count := 0;
END LOOP;
END insert_sku_prom_assigments;
#jorge is right there probably isn't much performance improvement to be found. But you could consider these alternatives to selecting a count before inserting:
This:
INSERT INTO mtep_sku_promotion_rel_unver
(sku_id, assignment, promotion_id
)
SELECT sku, assign, promotion FROM DUAL
WHERE NOT EXISTS
( SELECT NULL
FROM mtep_sku_promotion_rel_unver
WHERE sku_id = sku
AND assignment = assign
AND promotion_id = promotion
);
Or, assuming those 3 columns are defined as a key:
BEGIN
INSERT INTO mtep_sku_promotion_rel_unver
(sku_id, assignment, promotion_id
)
VALUES (sku, assign, promotion
);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END;
This is not performance-related, but I also like to use the Oracle function apex_util.string_to_table to break up delimited strings easily:
PROCEDURE ...
IS
v_tab apex_application_global.vc_arr2;
...
BEGIN
v_tab := apex_util.string_to_table (sku_assigment_array (i), ':');
if v_tab.count >= 3 then
sku := v_tab(1);
assign := v_tab(2);
promotion := v_tab(3);
end if;
Replace the row-by-row PL/SQL with a single SQL statement:
create or replace procedure insert_sku_prom_assigments
(
sku_assigment_array tp_string_array_table
) is
begin
--#4: Insert new SKU, ASSIGN, and PROMOTION.
insert into mtep_sku_promotion_rel_unver(sku_id, assignment, promotion_id)
select sku, assign, promotion
from
(
--#3: Get values.
select sku_string,
substr (sku_string, 1, first_colon_pos - 1) sku,
substr (sku_string, first_colon_pos + 1, second_colon_pos-first_colon_pos - 1) assign,
substr (sku_string, second_colon_pos + 1) promotion
from
(
--#2: Get positions.
select
sku_string,
instr(sku_string, ':', 1, 1) first_colon_pos,
instr(sku_string, ':', 1, 2) second_colon_pos
from
(
--#1: Convert collection to table.
select column_value sku_string
--Use this for debugging:
--from table(tp_string_array_table('asdf:qwer:1234'))
from table(sku_assigment_array)
) converted_collection
) positions
) sku_values
--Only non-null values:
where sku is not null and assign is not null and promotion is not null
--Row does not already exist:
and not exists
(
select 1/0
from mtep_sku_promotion_rel_unver
where sku_id = sku
and assignment = assign
and promotion_id = promotion
);
end insert_sku_prom_assigments;
/
Here are sample objects for testing:
create or replace type tp_string_array_table is table of varchar2(4000);
create table mtep_sku_promotion_rel_unver(
sku_id varchar2(100),
assignment varchar2(100),
promotion_id varchar2(100));
begin
insert_sku_prom_assigments(tp_string_array_table('asdf:qwer:1234'));
end;
/
SQL almost always outperforms PL/SQL. It's also easier to test and debug once you get used to the way the code flows.
Related
I need an Oracle (11) function to handle this question.
I need to counting distinct values count from comma separated string.
For example the comma separated string:
'Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text'
The result have to be = 6
Beacuse of
Lorem
Ipsum
is
simply
dummy
text
I want to use like this
select fn_dist_count_values_in_list_arr('Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text') from dual;
Can anyone help to write this ("fn_dist_count_values_in_list_arr") oracle function?
You don't need a context switch from SQL to a PL/SQL function and can do it all in SQL:
SELECT ( SELECT COUNT( DISTINCT CAST(column_value AS VARCHAR2(20)) )
FROM XMLTABLE( ('"'||REPLACE(value,',','","')||'"') ) )
AS num_distinct_values
FROM table_name
Which, for the sample data:
CREATE TABLE table_name ( value ) AS
SELECT 'Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text' FROM DUAL;
Outputs:
| NUM_DISTINCT_VALUES |
| ------------------: |
| 6 |
If you want a pure PL/SQL function (so that you do not have multiple context-switches) then:
CREATE FUNCTION fn_dist_count_values_in_list_arr (
list_value IN VARCHAR2
) RETURN NUMBER DETERMINISTIC
IS
TYPE t_words IS TABLE OF NUMBER(1,0) INDEX BY VARCHAR2(200);
v_words t_words;
v_start PLS_INTEGER := 1;
v_end PLS_INTEGER;
BEGIN
IF list_value IS NULL THEN
RETURN 0;
END IF;
LOOP
v_end := INSTR( list_value, ',', v_start );
EXIT WHEN v_end = 0;
v_words(SUBSTR(list_value, v_start, v_end - v_start ) ) := 1;
v_start := v_end + 1;
END LOOP;
v_words(SUBSTR(list_value,v_start)) := 1;
RETURN v_words.COUNT;
END;
/
and then:
SELECT fn_dist_count_values_in_list_arr( value )
FROM table_name
outputs:
| FN_DIST_COUNT_VALUES_IN_LIST_ARR(VALUE) |
| --------------------------------------: |
| 6 |
db<>fiddle here
CREATE OR REPLACE FUNCTION DIST_COUNT_VALUES_IN_STR_ARR
(STR_ARR IN VARCHAR2)
RETURN NUMBER
AS
DIST_COUNT NUMBER(38);
BEGIN
SELECT COUNT(DISTINCT COL1)
INTO DIST_COUNT FROM (
SELECT REGEXP_SUBSTR(STR_ARR,'[^,]+', 1, LEVEL) COL1
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(STR_ARR, ',') + 1
);
RETURN DIST_COUNT;
END;
This worked for me. The inner query separates the elements into rows by using regex on the comma character. I had to rename your function as i hit the limit of the max length of an object name for my version of Oracle.
And another approach would be to use hierarchical query to split comma separated values to a set of rows (with clause) and run a simple sql query against it
with str_parsed as (SELECT REGEXP_SUBSTR('Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text', '[^,]+', 1, LEVEL) val
FROM dual
CONNECT BY REGEXP_SUBSTR('Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text,Lorem,Ipsum,is,simply,dummy,text', '[^,]+', 1, LEVEL) IS NOT NULL)
select count(distinct val) from str_parsed
How to write Oracle stored procedure with a table (X) as input parameter and that table X is used inside procedure to join with another table Y?
Table X will have thousands of records.
Not looking to pass table name as varchar and then using dynamic SQL (so, this option is out of picture)
From 19.6 you can create a SQL macro. This returns a string with your query fragment.
At parse time the database will do a find/replace of the table parameter with the table you've passed it:
create or replace function f ( tab dbms_tf.table_t )
return varchar2 sql_macro as
begin
return 'select * from tab
join ( select level rn from dual connect by level <= 2 )
on c1 = rn';
end f;
/
create table t1 (
c1 int
);
create table t2 (
c1 int
);
insert into t1 values ( 1 );
insert into t2 values ( 2 );
select * from f ( t1 );
C1 RN
1 1
select * from f ( t2 );
C1 RN
2 2
There's another approach you might find interesting: pass a cursor variable to pipelined table function, invoke it in SQL, allowing you literally pass the contents of the table (select * from...), bulk collect into collection, then join the collection with your other table!
DROP TYPE tickertype FORCE;
DROP TYPE tickertypeset FORCE;
DROP TABLE stocktable;
DROP TABLE tickertable;
CREATE TABLE stocktable
(
ticker VARCHAR2 (20),
trade_date DATE,
open_price NUMBER,
close_price NUMBER
)
/
BEGIN
FOR indx IN 1 .. 100
LOOP
INSERT INTO stocktable
VALUES ('STK' || indx,
SYSDATE,
indx,
indx + 15);
END LOOP;
COMMIT;
END;
/
CREATE TABLE tickertable
(
ticker VARCHAR2 (20),
pricedate DATE,
pricetype VARCHAR2 (1),
price NUMBER
)
/
CREATE TYPE tickertype AS OBJECT
(
ticker VARCHAR2 (20),
pricedate DATE,
pricetype VARCHAR2 (1),
price NUMBER
);
/
BEGIN
FOR indx IN 1 .. 100
LOOP
INSERT INTO tickertable
VALUES ('STK' || indx,
SYSDATE,
'O',
indx);
END LOOP;
COMMIT;
END;
/
CREATE TYPE tickertypeset AS TABLE OF tickertype;
/
CREATE OR REPLACE PACKAGE refcur_pkg
AUTHID DEFINER
IS
TYPE refcur_t IS REF CURSOR
RETURN stocktable%ROWTYPE;
TYPE dataset_tt IS TABLE OF stocktable%ROWTYPE;
END refcur_pkg;
/
CREATE OR REPLACE FUNCTION pipeliner (dataset refcur_pkg.refcur_t)
RETURN tickertypeset
PIPELINED
AUTHID DEFINER
IS
l_row_as_object tickertype
:= tickertype (NULL,
NULL,
NULL,
NULL);
l_dataset refcur_pkg.dataset_tt;
l_count PLS_INTEGER;
BEGIN
FETCH dataset BULK COLLECT INTO l_dataset;
CLOSE dataset;
/* Let's do a join with another table. */
SELECT COUNT (*) into l_count
FROM TABLE (l_dataset) st, tickertable tt
WHERE st.ticker = tt.ticker;
DBMS_OUTPUT.put_line ('Count = ' ||l_count);
l_row_as_object.ticker := 'ABC';
PIPE ROW (l_row_as_object);
RETURN;
END;
/
BEGIN
FOR rec
IN (SELECT * FROM TABLE (pipeliner (CURSOR (SELECT * FROM stocktable))))
LOOP
DBMS_OUTPUT.put_line (rec.ticker);
END LOOP;
END;
/
I see this output:
Count = 100
ABC
Create a table type in the SQL scope:
CREATE TYPE string_list AS TABLE OF VARCHAR2(5);
Then use that as the parameter for your stored procedure and join it to another table using a Table Collection Expression:
CREATE PROCEDURE test_proc(
p_list IN string_list
)
IS
v_cursor SYS_REFCURSOR;
v_string VARCHAR2(10);
BEGIN
OPEN v_cursor FOR
SELECT d.*
FROM DUAL d
INNER JOIN TABLE( p_list ) t
ON ( d.DUMMY = t.COLUMN_VALUE );
-- do something with the cursor.
LOOP
FETCH v_cursor into v_string;
EXIT WHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_string );
END LOOP;
END;
/
Then you can call it:
BEGIN
test_proc( string_list( 'X', 'Y', 'Z' ) ) ;
END;
/
and it outputs:
X
db<>fiddle here
I have a Stored procedure in PLSQL which Inserts and Updates records on the basis of some condition.
Now here the issue is. While Inserting the record for the first time, it inserts records properly as required but
while updating it doesn't updates the record of the table.
Below is SP
PROCEDURE INSERT_INTO_VSAT_MST_DATA
(
P_SAPID IN NVARCHAR2,
P_CIRCLE IN NVARCHAR2,
P_CANDIDATEID IN NVARCHAR2,
P_SITEID IN NVARCHAR2,
P_PRIORITYID IN NVARCHAR2,
P_SITENAME IN NVARCHAR2,
P_LATITUDE IN NVARCHAR2,
P_LONGITUDE IN NVARCHAR2,
P_CONTACT_DETAILS IN CLOB,
P_SITETYPE IN NVARCHAR2,
P_SITE_PLOT_DIMENSION IN NUMBER,
P_TECHNOLOGY IN NVARCHAR2
)
AS
V_COUNT NUMBER:=0;
V_PANAROMICIMG_COUNT NUMBER:=0;
V_SATELLITEIMG_COUNT NUMBER:=0;
V_SITEPLOTIMG_COUNT NUMBER:=0;
V_VSAT_DETAIL_ID NUMBER:=0;
BEGIN
SELECT COUNT(VSAT_DETAIL_ID) INTO V_COUNT FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
IF V_COUNT > 0 THEN
SELECT VSAT_DETAIL_ID INTO TBL_INSERT FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
UPDATE TBL_VSAT_MST_DETAIL SET
CIRCLE = P_CIRCLE,
CONTACT_DETAILS = P_CONTACT_DETAILS,
SITE_TYPE = P_SITETYPE,
SITE_DETAILS_DIMENSION = P_SITE_PLOT_DIMENSION,
SITE_DETAILS_TECHNOLOGY = P_TECHNOLOGY
WHERE VSAT_DETAIL_ID = V_VSAT_DETAIL_ID
RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
ELSE
INSERT INTO TBL_VSAT_MST_DETAIL
(
SAP_ID,
CIRCLE,
CANDIDATE_ID,
SITE_ID,
PRIORITY,
SITE_NAME,
LATITUDE,
LONGITUDE,
CONTACT_DETAILS,
SITE_TYPE,
SITE_DETAILS_DIMENSION,
SITE_DETAILS_TECHNOLOGY
VALUES
(
P_SAPID,
P_CIRCLE,
P_CANDIDATEID,
P_SITEID,
P_PRIORITYID,
P_SITENAME,
P_LATITUDE,
P_LONGITUDE,
P_CONTACT_DETAILS,
P_SITETYPE,
P_SITE_PLOT_DIMENSION,
P_TECHNOLOGY
) RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
END IF;
IF TBL_INSERT > 0 THEN
BEGIN
SELECT COUNT(*) INTO V_PANAROMICIMG_COUNT FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Panaromic' AND IMG_ID = TBL_INSERT;
SELECT COUNT(*) INTO V_SATELLITEIMG_COUNT FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Satellite' AND IMG_ID = TBL_INSERT;
SELECT COUNT(*) INTO V_SITEPLOTIMG_COUNT FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'SitePlot' AND IMG_ID = TBL_INSERT;
IF V_PANAROMICIMG_COUNT > 0 THEN
BEGIN
DELETE FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Panaromic' AND IMG_ID = TBL_INSERT;
END;
END IF;
IF V_SATELLITEIMG_COUNT > 0 THEN
BEGIN
DELETE FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Satellite' AND IMG_ID = TBL_INSERT;
END;
END IF;
IF V_SITEPLOTIMG_COUNT > 0 THEN
BEGIN
DELETE FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'SitePlot' AND IMG_ID = TBL_INSERT;
END;
END IF;
FOR PMULTIFIELDS IN (SELECT REGEXP_SUBSTR(P_PANORAMIC_IMAGES,'[^,]+', 1, LEVEL) AS IMAGES FROM DUAL
CONNECT BY REGEXP_SUBSTR(P_PANORAMIC_IMAGES, '[^,]+', 1, LEVEL) IS NOT NULL
)
LOOP
INSERT INTO TBL_VSAT_IMAGE_DETAIL
(
IMG_ID,
IMG_NAME,
IMG_TYPE,
IMG_UPLOADED_DATE,
UPLOADED_BY
)
VALUES
(
TBL_INSERT,
PMULTIFIELDS.IMAGES,
'Panaromic',
SYSDATE,
P_CREATEDBY
);
END LOOP;
FOR PSATELLITEIMG IN (SELECT REGEXP_SUBSTR(P_SATELLITE_IMAGES,'[^,]+', 1, LEVEL) AS IMAGES FROM DUAL
CONNECT BY REGEXP_SUBSTR(P_SATELLITE_IMAGES, '[^,]+', 1, LEVEL) IS NOT NULL
)
LOOP
INSERT INTO TBL_VSAT_IMAGE_DETAIL
(
IMG_ID,
IMG_NAME,
IMG_TYPE,
IMG_UPLOADED_DATE,
UPLOADED_BY
)
VALUES
(
TBL_INSERT,
PSATELLITEIMG.IMAGES,
'Satellite',
SYSDATE,
P_CREATEDBY
);
END LOOP;
IF P_SITEPLOT_IMAGES IS NOT NULL THEN
BEGIN
INSERT INTO TBL_VSAT_IMAGE_DETAIL
(
IMG_ID,
IMG_NAME,
IMG_TYPE,
IMG_UPLOADED_DATE,
UPLOADED_BY
)
VALUES
(
TBL_INSERT,
P_SITEPLOT_IMAGES,
'SitePlot',
SYSDATE,
P_CREATEDBY
);
END;
END IF;
END;
END IF;
COMMIT;
EXCEPTION WHEN OTHERS THEN
ROLLBACK;
NOTE
While updating the record my TBL_INSERT returns as NULL
Expanding on what #user7294900 pointed you towards... in the declare section you have:
V_VSAT_DETAIL_ID NUMBER:=0;
then if v_count > 0 you do:
SELECT VSAT_DETAIL_ID INTO TBL_INSERT FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
UPDATE TBL_VSAT_MST_DETAIL SET
CIRCLE = P_CIRCLE,
CONTACT_DETAILS = P_CONTACT_DETAILS,
SITE_TYPE = P_SITETYPE,
SITE_DETAILS_DIMENSION = P_SITE_PLOT_DIMENSION,
SITE_DETAILS_TECHNOLOGY = P_TECHNOLOGY
WHERE VSAT_DETAIL_ID = V_VSAT_DETAIL_ID
RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
The select is setting TBL_INSERT to the ID value from your table. But when you do the update your filter is using V_VSAT_DETAIL_ID, which is still set to its initial value of zero.
You probably meant to do:
SELECT VSAT_DETAIL_ID INTO V_VSAT_DETAIL_ID FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
although you could still with your current select, and use that in the update too (making the returning into a bit redundant.
Be aware though that if v_count is not exactly 1, i.e. you have more than one row matching the P_SAPID and P_CANDIDATEID values, the select will get a too-many-rows exception. You won't see that because you are silently squashing any errors you get at run time.
It's usually not a good idea to commit or rollback inside a procedure anyway; it should be up to the caller to decide what to do, as this could be one of a series of statements and calls that you really want to treat as an atomic transaction. (You may be interested in savepoints.)
If you really, really want to rollback on exception within the procedure, you should at least re-raise the exception so the caller knows there was a problem:
EXCEPTION WHEN OTHERS THEN
ROLLBACK;
RAISE;
END INSERT_INTO_VSAT_MST_DATA;
but I would avoid when others if you can.
You could also combine a few steps by getting the ID at the start (again kind of assuming there is at most one matching row), and dropping the separate count and v_count variable:
SELECT MAX(VSAT_DETAIL_ID) INTO V_VSAT_DETAIL_ID
FROM TBL_VSAT_MST_DETAIL
WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
IF V_VSAT_DETAIL_ID IS NOT NULL THEN
UPDATE TBL_VSAT_MST_DETAIL SET
CIRCLE = P_CIRCLE,
CONTACT_DETAILS = P_CONTACT_DETAILS,
SITE_TYPE = P_SITETYPE,
SITE_DETAILS_DIMENSION = P_SITE_PLOT_DIMENSION,
SITE_DETAILS_TECHNOLOGY = P_TECHNOLOGY
WHERE VSAT_DETAIL_ID = V_VSAT_DETAIL_ID
RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
ELSE
...
And I'm not sure why you're doing counts before your deletes later on, and it looks like all your tbl_insert references could/should be v_vast_detail_id - there doesn't seem an obvious reason to have two variables for that. Passing in a comma-delimited string that you then have to tokenize is also a bit painful - you should consider passing in a collection of values instead, if whatever calls this can manage that.
As also pointed out, you could use merge instead of the separate insert/update logic.
You don't assign value to V_VSAT_DETAIL_ID which is used in your update as a key.
You should use merge for this kind of operations
After creating all tables in SQL Developer I need to fill them with at least 10000 entries. I had no problem with tables than had zero FK.
How can I get random values from other tables?
select rand() doesn't work in this statement. but max() does.
CREATE OR REPLACE PROCEDURE NAPOLNI_ARTIKEL
(
ST_ARTIKLOV IN VARCHAR2 DEFAULT 10000
) AS
naziv VARCHAR2(25);
opis VARCHAR2(25);
model VARCHAR2(10);
cena FLOAT(2);
gar INTEGER;
ddv INTEGER;
tip INTEGER;
CURSOR c1 IS
SELECT id_dobavitelj
FROM dobavitelj;
BEGIN
FOR rndx IN c1 LOOP
FOR st IN 1..ST_ARTIKLOV LOOP
naziv := 'naziv';
naziv := naziv ||' '|| TO_CHAR(st);
opis := 'opis';
opis := opis ||' '|| TO_CHAR(st);
model := 'model';
model := model ||' '|| TO_CHAR(st);
cena := dbms_random.value(1.25,230.0);
SELECT NVL(RAND(id_garancija),1)
INTO gar
FROM garancija;
SELECT NVL(RAND(id_ddv),1)
INTO ddv
FROM DDV;
SELECT NVL(RAND(id_tip),1)
INTO tip
FROM tip;
INSERT INTO ARTIKEL(ID_ARTIKLA, NAZIV, OPIS, MODEL, CENA, TIP_ID_TIP, DOBAVITELJ_ID_DOBAVITELJ, GARANCIJA_ID_GARANCIJE, DDV_ID_DDV) VALUES (st, naziv, opis, model, cena, tip, rndx, gar, ddv);
END LOOP;
END LOOP;
END NAPOLNI_ARTIKEL;
* id_artikla INTEGER NOT NULL ,
* naziv VARCHAR2 (25) NOT NULL ,
opis VARCHAR2 (25) ,
* model VARCHAR2 (10) NOT NULL ,
* cena FLOAT (2) NOT NULL ,
F * Tip_id_tip INTEGER NOT NULL ,
F * Dobavitelj_id_dobavitelj INTEGER NOT NULL ,
F Garancija_id_garancije INTEGER ,
F * DDV_id_ddv INTEGER NOT NULL
To pick a random value from a table you can use a subquery ordered randomly, and then pick the first row from that:
SELECT id_garancija
INTO gar
FROM (
SELECT id_garancija
FROM garancija
ORDER BY dbms_random.value
)
WHERE rownum = 1;
With a lot of data you could also use the sample() clause to avoid having to find and order all values from the table:
SELECT id_garancija
INTO gar
FROM garancija
SAMPLE(1)
WHERE rownum = 1;
You can set the sample size based on the size of the table; with 10000 rows you might be able to use 0.1, for example. You can read more here.
If you're populating your parent and child tables at the same time you could consider the INSERT ALL syntax to insert into multiple tables simultaneously, using the same values (eg from a sequence) consistently, rather than looking them up again later. It looks like you want random combinations of foreign keys though, so that might not be helpful here.
I'm not quite understanding the solution found here:
Selecting Values from Oracle Table Variable / Array?
I have a list of table names. I would like to loop thru them as an array, using their values as tables to search from.
TMP_DORMANT_FILTERS physical table of table names. The array below is the same list.
LM_DORMANT_EMAIL is a list of email addresses.
I want to check the existence of the dormant email addresses in the list of tables. I realize I could write the same query 12 times to search each table. But that's not going to improve my SQL skills.
Here is my array attempt. In this attempt, Oracle doesn't like the way I'm calling the array value in my where not exists sql:
DECLARE
TYPE array_t IS VARRAY(12) OF VARCHAR2(25);
ARRAY array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
BEGIN
FOR i IN 1..array.count LOOP
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM array(i) hist
WHERE ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
);
COMMIT;
END LOOP;
END;
/
Or using the solution found at the link above, I tried. Oracle doesn't recognize my inserting into dormant_filters under the begin part. It's telling me my physical table TMP_DORMANT_FILTERS does not exist:
CREATE GLOBAL TEMPORARY TABLE dormant_filters
( filters varchar2(100)
)
ON COMMIT DELETE ROWS;
BEGIN
INSERT INTO dormant_filters
( filters )
( SELECT TABLE_NAMES
FROM TMP_DORMANT_FILTERS
);
FOR j IN ( SELECT filters FROM dormant_filters ) LOOP
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS, j as DORMANT_SOURCE
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM j hist
WHERE feed.ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
);
NULL;
END LOOP;
COMMIT;
END;
/
This problem requires dynamic SQL. Bind variables can be used for values but not for objects.
declare
type array_t is varray(12) of varchar2(25);
array array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
begin
for i in 1 .. array.count loop
execute immediate '
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM '||array(i)||' hist
WHERE ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
)
';
commit;
end loop;
end;
/
UPDATE
If the column names are different for each table you can use the data dictionary to pick the correct column name.
declare
type array_t is varray(12) of varchar2(25);
array array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
v_column_name varchar2(30);
begin
for i in 1 .. array.count loop
select column_name
into v_column_name
from all_tab_columns
where owner = 'SCHEMA NAME'
and table_name = array(i)
and column_name in ('ACTIVITY_TIME','DATE_ABANDONED');
execute immediate '
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM '||array(i)||' hist
WHERE '||v_column_name||' >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
)
';
commit;
end loop;
end;
/