Handle null values in BULK COLLECT - oracle

I trying to pass data from table A to table B, but first I need to check if the data I'm trying to insert is not in the table B. I do this with a query if exists will return the identification. The problem is the second execution throws a unique constraint violation so it appears the validation to compare if exist in table B is not working, or the condition in the if statement is wrong.
DECLARE
LN_EXIST NUMBER;
CURSOR cur
IS
SELECT *
FROM table_A
TYPE cur_aat IS TABLE OF cur%ROWTYPE
INDEX BY PLS_INTEGER;
cur_rows cur_aat;
BEGIN
OPEN cur;
LOOP
FETCH cur BULK COLLECT INTO cur_rows LIMIT 1000;
EXIT WHEN cur%NOTFOUND; /* cause of missing rows */
FOR I IN 1 .. cur_rows.COUNT
LOOP
LN_EXIST := 0;
BEGIN
DBMS_OUTPUT.put_line (cur_rows (I).PEU_IDENTIFICACION);
SELECT PUV.PEU_IDENTIFICACION -- check
INTO LN_EXIST
FROM table_b PUV
WHERE (CASE
WHEN PUV.PEU_IDENTIFICACION =
cur_rows (I).PEU_IDENTIFICACION
AND NVL (PUV.PEU_PRIMER_NOMBRE, '0') =
NVL (cur_rows (I).PEU_PRIMER_NOMBRE, '0')
AND NVL (PUV.PEU_SEGUNDO_NOMBRE, '0') =
NVL (cur_rows (I).PEU_SEGUNDO_NOMBRE, '0')
AND PUV.PEU_PRIMER_APELLIDO =
cur_rows (I).PEU_PRIMER_APELLIDO
AND NVL (PUV.PEU_SEGUNDO_APELLIDO, '0') =
NVL (cur_rows (I).PEU_SEGUNDO_APELLIDO,
'0')
AND PUV.PEU_FECHA_NACIMIENTO =
cur_rows (I).PEU_FECHA_NACIMIENTO
THEN
'S'
ELSE
'N'
END) = 'S';
EXCEPTION
WHEN OTHERS
THEN
LN_EXIST:= 0;
END;
IF LN_EXIST!= 0 --if not exist
THEN
INSERT
INTO table_b (PEU_ID,
PEU_IDENTIFICACION,
PEU_PRIMER_APELLIDO,
PEU_SEGUNDO_APELLIDO,
PEU_PRIMER_NOMBRE,
PEU_SEGUNDO_NOMBRE,
)
VALUES (cur_rows (I).PEU_ID,
cur_rows (I).PEU_TIPO_IDENTIFICACION,
cur_rows (I).PEU_IDENTIFICACION,
cur_rows (I).PEU_PRIMER_APELLIDO,
cur_rows (I).PEU_SEGUNDO_APELLIDO,
cur_rows (I).PEU_PRIMER_NOMBRE,
cur_rows (I).PEU_SEGUNDO_NOMBRE,
);
END IF;
END LOOP;
EXIT WHEN cur%NOTFOUND;
END LOOP;
COMMIT;
CLOSE cur;
END;

First of all, the code shown above won't execute because of syntax errors in the lines which read
PEU_SEGUNDO_NOMBRE,
and
cur_rows (I).PEU_SEGUNDO_NOMBRE,
The trailing commas will cause the compilation to fail.
Second, row-by-row processing tends to be slow, even when you bulk collect the data into memory from the database. I suggest you use a MERGE, which replaces your cursor-and-loop logic with a single statement:
MERGE INTO TABLE_B b
USING TABLE_A a
ON (b.PEU_IDENTIFICACION = a.PEU_IDENTIFICACION AND
NVL(b.PEU_PRIMER_NOMBRE, '0') = NVL (a.PEU_PRIMER_NOMBRE, '0') AND
NVL (b.PEU_SEGUNDO_NOMBRE, '0') = NVL(a.PEU_SEGUNDO_NOMBRE, '0') AND
b.PEU_PRIMER_APELLIDO = a.PEU_PRIMER_APELLIDO AND
NVL(b.PEU_SEGUNDO_APELLIDO, '0') = NVL(a.PEU_SEGUNDO_APELLIDO, '0') AND
b.PEU_FECHA_NACIMIENTO = a.PEU_FECHA_NACIMIENTO)
WHEN NOT MATCHED THEN
INSERT (PEU_ID,
PEU_IDENTIFICACION,
PEU_PRIMER_APELLIDO,
PEU_SEGUNDO_APELLIDO,
PEU_PRIMER_NOMBRE,
PEU_SEGUNDO_NOMBRE)
VALUES (a.PEU_ID,
a.PEU_TIPO_IDENTIFICACION,
a.PEU_IDENTIFICACION,
a.PEU_PRIMER_APELLIDO,
a.PEU_SEGUNDO_APELLIDO,
a.PEU_PRIMER_NOMBRE,
a.PEU_SEGUNDO_NOMBRE);
Best of luck.

I checked, instead of use a select statement only, i added a count statement, to determine the total of coincidences that exist with the parameters i set so all the times will return a value if not exist 0 and if exist other value than 0.
DECLARE
LN_EXIST NUMBER;
CURSOR cur
IS
SELECT *
FROM table_A
TYPE cur_aat IS TABLE OF cur%ROWTYPE
INDEX BY PLS_INTEGER;
cur_rows cur_aat;
BEGIN
OPEN cur;
LOOP
FETCH cur BULK COLLECT INTO cur_rows LIMIT 1000;
EXIT WHEN cur%NOTFOUND; /* cause of missing rows */
FOR I IN 1 .. cur_rows.COUNT
LOOP
DBMS_OUTPUT.put_line (cur_rows (I).PEU_IDENTIFICACION);
SELECT COUNT (PUV.PEU_IDENTIFICACION) -- check
INTO LN_EXIST
FROM table_b PUV
WHERE (CASE
WHEN PUV.PEU_IDENTIFICACION =
cur_rows (I).PEU_IDENTIFICACION
AND NVL (PUV.PEU_PRIMER_NOMBRE, '0') =
NVL (cur_rows (I).PEU_PRIMER_NOMBRE, '0')
AND NVL (PUV.PEU_SEGUNDO_NOMBRE, '0') =
NVL (cur_rows (I).PEU_SEGUNDO_NOMBRE, '0')
AND PUV.PEU_PRIMER_APELLIDO =
cur_rows (I).PEU_PRIMER_APELLIDO
AND NVL (PUV.PEU_SEGUNDO_APELLIDO, '0') =
NVL (cur_rows (I).PEU_SEGUNDO_APELLIDO,
'0')
AND PUV.PEU_FECHA_NACIMIENTO =
cur_rows (I).PEU_FECHA_NACIMIENTO
THEN
'S'
ELSE
'N'
END) = 'S';
IF LN_EXIST!= 0 --if not exist
THEN
INSERT
INTO table_b (PEU_ID,
PEU_IDENTIFICACION,
PEU_PRIMER_APELLIDO,
PEU_SEGUNDO_APELLIDO,
PEU_PRIMER_NOMBRE,
PEU_SEGUNDO_NOMBRE,
)
VALUES (cur_rows (I).PEU_ID,
cur_rows (I).PEU_TIPO_IDENTIFICACION,
cur_rows (I).PEU_IDENTIFICACION,
cur_rows (I).PEU_PRIMER_APELLIDO,
cur_rows (I).PEU_SEGUNDO_APELLIDO,
cur_rows (I).PEU_PRIMER_NOMBRE,
cur_rows (I).PEU_SEGUNDO_NOMBRE,
);
END IF;
END LOOP;
EXIT WHEN cur%NOTFOUND;
END LOOP;
COMMIT;
CLOSE cur;
END;

Related

Updating column is not working in Oracle PLSQL

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

ORA-01403: no data found -- Exception handling not working

BEGIN
FOR v_LoadRec IN c_Load LOOP
SELECT count(1) INTO v_NO_OF_DAYS_RESP
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESPONSE_DATE,'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESP :='';
END IF;
SELECT count(1) INTO v_NO_OF_DAYS_RESO
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESOLVE_DATE,'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESO :='';
END IF;
END LOOP;
I have this block of SQL in my update procedure which gathers the count of trading days for each record and then inserts it into an integer variable named "v_NO_OF_DAYS_RESP" e.g. count of days between the open and response date of a call.
This works well except for when there is a null "RESPONSE_DATE" where it fails with the "ORA-01403: no data found" error. I understand why it's failing (because it of course has no record to insert) but I can't seem to figure out a way to get around it.
In these circumstances where the "RESPONSE_DATE" is found to be NULL, I would like the "v_NO_OF_DAYS_RESP" var to be set to NULL too (or even somehow have the SQL statement nested within an "IF" to possibly completely avoid running the calculation (SQL statement) when the "RESPONSE_DATE" is NULL).
*To put it really simple, I want the following:.. If the call does not yet have a response date, either don't run the SQL statement (calculation) or just set the var to Null
Any ideas or suggestions would be greatly appreciated.
Thanks - Kelvin
Handling exception will solve your problem:
BEGIN
FOR v_LoadRec IN c_Load LOOP
SELECT count(1) INTO v_NO_OF_DAYS_RESP
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESPONSE_DATE,'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESP :='';
END IF;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_NO_OF_DAYS_RESP :='';
END;
if (v_LoadRec.RESPONSE_DATE) is null Then
v_NO_OF_DAYS_RESP:='';
else
SELECT count(1) INTO v_NO_OF_DAYS_RESP
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESPONSE_DATE, 'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESP :='';
END IF;
end if;
Get the count of records in SELECT query. Then you can validate (count=0 or not).You can try like this.
result VARCHAR2(100);
result_count NUMBER;
BEGIN
SELECT count(<COLUMN_NAME>) INTO result_count FROM <TABLE_NAME> where empid = 12;
IF result_coun = 0 THEN
result := 'Value does not exist in the reference table';
END IF;
END;

IF statement contains SELECT statment

When running this:
create or replace FUNCTION GET_FUNCTION(STATUS_ID IN VARCHAR2)
RETURN VARCHAR2
IS STATUS_NAME VARCHAR2(255);
BEGIN
IF NVL(STATUS_ID) THEN
STATUS_NAME:='';
ELSIF STATUS_ID LIKE '0' THEN
STATUS_NAME:='UNASSIGNED'
ELSE
SELECT TABLE_A.NAME INTO STATUS_NAME FROM TABLE_A where
TABLE_A.ID=STATUS_ID;
END IF;
RETURN(STATUS_NAME);
end;
I get the following error:
Error(9,4): PLS-00103: Encountered the symbol "ELSE" when expecting one of the following:
* & = - + ; < / > at in is mod remainder not rem <an exponent (**)> <> or != or ~= >= <= <> and or like like2 like4 likec between || multiset member submultiset
The symbol ";" was substituted for "ELSE" to continue.
What is the problem?
CREATE OR REPLACE FUNCTION GET_FUNCTION(STATUS_ID IN VARCHAR2)
RETURN VARCHAR2
IS STATUS_NAME VARCHAR2(255);
BEGIN
IF NVL(STATUS_ID) THEN
STATUS_NAME := '';
ELSIF STATUS_ID = '0' THEN
STATUS_NAME := 'UNASSIGNED';
ELSE
SELECT TABLE_A.NAME INTO STATUS_NAME
FROM TABLE_A
WHERE TABLE_A.ID = STATUS_ID;
END IF;
RETURN STATUS_NAME;
END;
I think that is one is a little bit beter
CREATE OR REPLACE FUNCTION GET_FUNCTION(STATUS_ID IN VARCHAR2)
RETURN VARCHAR2
IS
cursor c_tab (B_STATUS_ID IN TABLE_A.ID%TYPE)
is
SELECT TABLE_A.NAME
FROM TABLE_A
WHERE TABLE_A.ID = B_STATUS_ID;
STATUS_NAME VARCHAR2(255);
BEGIN
IF NVL(STATUS_ID, -1) = -1 THEN
-- IF STATUS_ID IS NULL THEN ==> This one is also possible!
STATUS_NAME := '';
ELSIF STATUS_ID = '0' THEN
STATUS_NAME := 'UNASSIGNED';
ELSE
OPEN C_TAB (STATUS_ID);
FETCH C_TAB INTO STATUS_NAME;
CLOSE C_TAB;
END IF;
RETURN STATUS_NAME;
END;
You need to add semicolon after each statement to execute (line 8 in your code - I've added a comment):
create or replace FUNCTION GET_FUNCTION(STATUS_ID IN VARCHAR2)
RETURN VARCHAR2
IS STATUS_NAME VARCHAR2(255);
BEGIN
IF NVL(STATUS_ID) THEN
STATUS_NAME:='';
ELSIF STATUS_ID LIKE '0' THEN
STATUS_NAME:='UNASSIGNED'; /* HERE HAS TO BE A SEMICOLON TOO */
ELSE
SELECT TABLE_A.NAME INTO STATUS_NAME FROM TABLE_A where
TABLE_A.ID=STATUS_ID;
END IF;
RETURN(STATUS_NAME);
end;

How to use BULK COLLECT and FORALL to replace CURSOR FOR LOOP in PL/SQL?

How to use BULK COLLECT and FORALL to replace CURSOR FOR LOOP in PL/SQL? I'd like to have a more efficient way to update records in a single table.
Suppose I have the following PL/SQL code:
DECLARE
var_buy_more_shoes inventory.buy_more_shoes%TYPE := NULL;
var_buy_more_bananas inventory.buy_more_bananas%TYPE := NULL;
var_buy_more_iphone6s inventory.buy_more_iphone6s%TYPE := NULL;
CURSOR cur
IS
SELECT *
FROM inventory
FOR UPDATE;
BEGIN
FOR rec IN cur
LOOP
IF rec.pair_of_shoes_left <= 100
THEN
var_buy_more_shoes := 'Yes';
END IF;
IF rec.weight_of_bananas_left <= 200
THEN
var_buy_more_bananas := 'Yes';
END IF;
IF rec.number_of_iphone6s_left <= 50
THEN
var_buy_more_iphone6s := 'Yes';
END IF;
UPDATE inventory a
SET A.buy_more_shoes = var_buy_more_shoes,
A.buy_more_bananas = var_buy_more_bananas,
A.buy_more_iphone6s = var_buy_more_iphone6s
WHERE CURRENT OF cur;
END LOOP;
COMMIT;
END;
Thanks.
This can be done in a single update statement:
UPDATE inventory
SET buy_more_shoes = CASE
WHEN pair_of_shoes_left <= 100 THEN 'Yes'
ELSE NULL
END
, buy_more_bananas = CASE
WHEN weight_of_bananas_left <= 200 THEN 'Yes'
ELSE NULL
END
, buy_more_iphone6s = CASE
WHEN number_of_iphone6s_left <= 50 THEN 'Yes'
ELSE NULL
END
You can create a table object and collections, then bulk collect the query results into the table collections.
CREATE OR REPLACE EDITIONABLE TYPE "F_OBJ" AS OBJECT (
Employer_name VARCHAR2(100),
Employer_id VARCHAR2 ( 100 ))
CREATE OR REPLACE EDITIONABLE TYPE "F_TAB" as table of _OBJ
create or replace function "fname"
return f_tab
is
l_f_tab f_tab;
begin
SELECT f_obj(employee_name, employee_id) bulk collect into f_tab from employee_table
return f_tab;

Dynamically add where clauses to a cursor in oracle

I have plsql procedure which accepts certain parameters e.g. v_name, v_country, v_type.
I wish to have a cursor with a select statement like this:
select column from table1 t1, table2 t2
where t1.name = v_name
and t1.country = v_country
and t1.id = t2.id
and t2.type = v_type
If certain parameters are empty can I only add the relevant where clauses to the cursor? Or is there a better way to accomplish this?
The best way to use this is with DBMS_SQL.
You create a string that represents your SQL statement. You still use bind variables. It's painful.
It goes something like this (I haven't compiled this, but it should be close) :-
CREATE OR REPLACE FUNCTION find_country( v_name t1.country%TYPE,
v_type t2.type%TYPE) /* Hmm, column called type? */
DECLARE
v_SQL varchar2(2000);
v_select INTEGER; /* "Pointer" to a DBMS_SQL select statement */
v_execute INTEGER;
BEGIN
v_SQL := 'select column from table1 t1, table2 t2 ||
'where t1.id = t2.id';
IF v_name IS NOT NULL THEN
v_SQL := v_SQL || ' AND t1.country = :v_name'
END IF;
IF v_type IS NOT NULL THEN
v_SQL := v_SQL || ' AND t2.type = :v_type';
END IF;
/* Setup Cursor */
v_select := dbms_sql.open_cursor;
dbms_sql.parse( v_select, v_SQL, DBMS_SQL.native);
IF v_name IS NOT NULL THEN
dbms_sql.bind_variable( v_select, ':v_name', v_name );
END IF;
IF v_type IS NOT NULL THEN
dbms_sql.bind_variable( v_select, ':v_type', v_type );
END IF;
DBMS_SQL.DEFINE_COLUMN(v_select, 1, v_column); /* This is what we have selected */
/* Return value from EXECUTE is undefined for a SELECT */
v_execute := DBMS_SQL.EXECUTE( v_select );
IF DBMS_SQL.FETCH_ROWS( v_select ) > 0 THEN
/* A row was found
DBMS_SQL.COLUMN_VALUE( v_select, 1, v_column);
/* Tidy Up */
DBMS_SQL.CLOSE_CURSOR(v_select);
RETURN v_ID_address;
ELSE
DBMS_SQL.CLOSE_CURSOR(v_select);
/* No row */
RETURN NULL;
END IF;
EXCEPTION
WHEN OTHERS THEN
IF DBMS_SQL.IS_open(v_select) THEN
DBMS_SQL.CLOSE_CURSOR(v_select);
END IF;
RAISE;
END;
This approach is so painful compared to just writing the SQL inline that unless you have heaps of columns sometimes it's just easier writing a couple of different versions using this syntax:
FOR r IN (SELECT blah FROM blah WHERE t1 = v_t1) LOOP
func( r.blah );
END LOOP;
It's not directly what you're asking, but it may be an acceptable solution:
select column from table1 t1, table2 t2
where
(v_name is null or t1.name = v_name)
and (v_country is null or t1.country = v_country)
and t1.id = t2.id
and (v_type is null or t2.type = v_type)
One way would be to build up your query as a string then use execute immediate
The best way to do this would be to use Oracle's Application Context feature, best defined as best performance and security.
The faster way would be what hamishmcn suggested, using EXECUTE IMMEDIATE. I'd choose that over WW's suggestion of DBMS_SQL every time.
Another way that's quickest to write but won't perform as well would be something like this:
select column from table1 t1, table2 t2
where t1.name = nvl(v_name, t1.name)
and t1.country = nvl(v_country, t1.country)
and t1.id = t2.id
and t2.type = nvl(v_type, t2.type)
You do not have to use dbms_sql to solve this problem
and you can still use normal cursor by using a ref cursor.
Sample:
DECLARE
TYPE cursor_ref IS REF CURSOR;
c1 cursor_ref;
r1 table1.column%type;
BEGIN
l_sql := 'select t1.column from table1 t1, table2 t2 where t1.id = t2.id ';
if v_name is not null then
l_sql := l_sql||' and t1.name = '||v_name ;
end if;
if v_country is not null then
l_sql := l_sql||' and t1.country = '||v_country';
end if;
if v_type is not null then
l_sql := l_sql||' and t2.type = '||v_type';
end if;
open c1 for l_sql;
loop
fetch c1 into r1;
exit when c1%notfound;
-- do something
end loop;
close c1;
end;
/
You can make this better by binding the variables with the command 'using' like this:
open c1 for l_sql using v_name, v_country;

Resources