PLSQL NULL or Empty collection in table SELECT - plsql

I am trying to figure out a clean way of qualifying on a table type that may be null or empty. Below is what I have tried so far but that results in a
ORA-01427: single-row subquery returns more than one row
DECLARE
V_TEMP_IDS V500.T_TEMP_IDS := V500.T_TEMP_IDS(123, 124);
TYPE T_RESULTS IS TABLE OF NUMBER;
V_RESULTS T_RESULTS;
BEGIN
SELECT TEMP_ID BULK COLLECT INTO V_RESULTS FROM PEOPLE
WHERE ACTIVE = 1
AND TEMP_ID IN (COALESCE(NULLIF((SELECT * FROM TABLE(V_TEMP_IDS)), 0), TEMP_ID));
dbms_output.put_line(SQL%ROWCOUNT);
END;
I would like to select all rows if the collection is null or empty but if it has entries, such as in this example, to include them in the qualification. Hope that makes sense!
Thanks in advance!

NULLIF((SELECT * FROM TABLE(V_TEMP_IDS)), 0) -- is not allowed.
If SELECT * FROM TABLE(V_TEMP_IDS) returns multiple rows, nullif won't work.
(SELECT NULLIF(*, 0) FROM TABLE(V_TEMP_IDS)) -- should be what you want to do..

If you want to check whether v_temp_ids is null or empty, use the is null and is empty operators:
declare
v_temp_ids t_temp_ids := t_temp_ids(123, 124);
type t_results is table of number;
v_results t_results;
begin
select temp_id bulk collect into v_results from people
where active = 1
and ( temp_id member of v_temp_ids
or v_temp_ids is empty
or v_temp_ids is null );
dbms_output.put_line(sql%rowcount);
end;
Test a null collection with
v_temp_ids t_temp_ids;
Test an empty collection with
v_temp_ids t_temp_ids := t_temp_ids();

Related

PL SQL: How to populate a cursor in loop

I have a scenario to implement where in we will have an SP with two parameters
1) In parameter of array of Varchar2
2) Cursor -- Out parameter
We have populate the cursor only for each Value passed in array and this opened/populated cursor will be used by Java. But what i have found till now is, if we have to process an array then it should have to be done using loop.
For ex:
Open Cursor_Name
For
For index In Arrar_Parameter.FIRST .. Arrar_Parameter.LAST
LOOP
SELECT * FROM EMP WHERE EmpId = Arrar_Parameter[i] -------> This needs to be looped and not sure if this will work
END LOOP
Can we have some thing like this
Open Cursor_Name
For
SELECT * FROM EMP WHERE EmpId IN (Arrar_Parameter values) ------> To fetch/put all the array values at once without loop.
Kindly suggest hot to populate cursor in this scenario
Assuming your array parameter is a schema-level varray or nested table, you can use a table collection expression:
The table_collection_expression lets you inform Oracle that the value of collection_expression should be treated as a table for purposes of query and DML operations. The collection_expression can be a subquery, a column, a function, or a collection constructor. Regardless of its form, it must return a collection value—that is, a value whose type is nested table or varray. This process of extracting the elements of a collection is called collection unnesting.
This uses a built-in varray type but you can substitute your own:
create procedure procedure_name (
array_parameter sys.odcivarchar2list, cursor_name out sys_refcursor
) as
begin
open cursor_name for
select e.*
from table (array_parameter) a
join emp e on e.empid = a.column_value;
end;
/
You can also use in if you prefer:
create procedure procedure_name (
array_parameter sys.odcivarchar2list, cursor_name out sys_refcursor
) as
begin
open cursor_name for
select *
from emp
where empid in (select column_value from table (array_parameter));
end;
/
If it is a nested table you can also use the member of syntax:
create type my_varchar2_table as table of varchar2(30);
/
create procedure procedure_name (
array_parameter my_varchar2_table, cursor_name out sys_refcursor
) as
begin
open cursor_name for
select *
from emp
where empid member of array_parameter;
end;
/

PL/SQL List of items, check if record exists, if yes update if not create

Im trying to learn PL/SQL and I was given an assignment which I am not sure how to tackle.
I am given a list of orders. I want to check my ORDER table for each of them in the following way:
Check if order exists, if no create a record
Check if order fullfilled (0 or 1)
If order is not fullfilled (0), update to 1
I put together a script which I think can do this for one order, but I'm sure it's not very good:
DECLARE
tmp NUMBER;
tmp2 NUMBER;
o_id NUMBER := 999;
BEGIN
/*Checking if order exists */
SELECT COUNT (*)
INTO tmp
FROM ORDERS
WHERE ORDERID = o_id;
IF ( tmp = 0 ) THEN
/* INSERT HERE */
END IF;
SELECT FULLFILLED INTO tmp2
FROM ORDERS
WHERE ORDERID = o_id;
IF (tmp2 = 0) THEN
/* UPDATE... */
END IF;
end;
I would appreciate any advice, what should I look into to make this script efficient? Thank you.
MERGE statement is what you need. It is based on SELECT statement and let's you UPDATE or INSERT data using it's WHEN (NOT) MATCHED THEN clauses. Here's a good explanation with some examples: Oracle Base MERGE Statement.
Here's also some code snippet you might find useful:
DECLARE
o_id NUMBER := 999;
BEGIN
MERGE INTO ORDERS o
USING
(SELECT o_id AS orderid FROM dual) o_id
ON
(o.orderid = o_id.orderid)
WHEN MATCHED THEN
UPDATE SET
o.fulfilled = CASE WHEN o.fulfilled = 0 THEN 1 ELSE o.fulfilled END
WHEN NOT MATCHED THEN
INSERT (fulfilled, <some_other_columns>)
VALUES (1, <values_for_other_columns>);
END;
/
Please read up on the merge statement: https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm
Also called an "upsert". Basically if the row does not exist, insert. If it does, update.
It does what you are trying to do in one statement.

Mixing arrays and queries

I have a set of product that I want to tag to a certain value so what I did this:
declare
type array_produit_auto is varray(3) of varchar(50);
array array_produit_auto := array_produit_auto('code_product1', 'code_product2', 'code_product3');
begin
for i in 1..array.count loop
update F_PRODUITASS pas
set PAS_NONGES_IDA = 0
WHERE PAS.PAS_CODE_PRODUIT = array(i;
end loop;
end;
commit;
however, the list of these products is too long. Instead I'd like to do this:
declare
type array_produit_auto is varray(3) of varchar(50);
array array_produit_auto := array_produit_auto('code_product4', 'code_product5', 'code_product6');
begin
update F_PRODUITASS pas
set PAS_NONGES_IDA = 1
WHERE PAS.PAS_CODE_PRODUIT NOT IN array;
end;
commit;
except this doesn't work since apparently I can't mix a query and an array this way.
Any idea of how I could make this work?
If you used a nested table then you could query from the nested table, something like this:
DECLARE
v_exclude_list t_array_produit_auto :=
t_array_produit_auto('code_product4', 'code_product5', 'code_product6');
BEGIN
UPDATE F_PRODUITASS pas
SET PAS_NONGES_IDA = 1
WHERE PAS.PAS_CODE_PRODUIT NOT IN ( SELECT *
FROM TABLE(v_exclude_list) )
;
END;
/
Also, you meant varchar2, right?
Update regarding the Opaque error: The type declaration would need to be an object type (create with the CREATE OR REPLACE TYPE syntax rather than a local plsql type as in the DDL below.
CREATE TABLE F_PRODUITASS(PAS_NONGES_IDA number, PAS_CODE_PRODUIT VARCHAR2(50));
INSERT INTO F_PRODUITASS VALUES(3, 'code_product3');
INSERT INTO F_PRODUITASS VALUES(4, 'code_product4');
CREATE OR REPLACE TYPE t_array_produit_auto IS TABLE OF VARCHAR2(50);
If you did not wish to create your own object type, you could use pre-existing varchar2 or number types such as sys.odcivarchar2list as described here:
Anonymous TABLE or VARRAY type in Oracle

Oracle 11g : When declaring new TYPE as TABLE, must I add "INDEX BY PLS_INTEGER"?

What is the diffecence between adding INDEX BY PLS_INTEGER and not at end of declaration of new table type. Look at this example:
DECLARE
GC_BULK_LIMIT CONSTANT INTEGER := 500;
CURSOR CUR_CLIENTS IS SELECT C.ID, C.NAME FROM CLIENTS C;
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
-- TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
LT_CLIENTS RT_CLIENTS;
BEGIN
OPEN CUR_CLIENTS;
LOOP
FETCH CUR_CLIENTS BULK COLLECT INTO LT_CLIENTS LIMIT GC_BULK_LIMIT;
EXIT WHEN LT_CLIENTS.COUNT = 0;
FOR I IN 1..LT_CLIENTS.COUNT LOOP
-- ... SOME LOGIC
END LOOP;
END LOOP;
CLOSE CUR_CLIENTS;
END;
in response to "must i add". The short answer is NO.
the difference is that
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
Is a nested table. This means that for a given variable of this type, we know that the subscripts are sequential. i.e. the subscript starts from 1 and goes up to the array length.
The following loop therefore, is the right way to access a nested table array:
FOR I IN 1..LT_CLIENTS.COUNT LOOP
This however, is called a associative array:
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
(you could also index by a varchar2 if you wanted). The difference is that the subscripts in this case do not have to be sequential, depending on how the array was populated. In your code, they would be (as bulk collect would do that), but its not always the case.
The safe way to access and loop through an index by array is :
v_subscript := t_arr.first;
while v_subscript is not null loop
dbms_output.put_line(v_subscript || ': ' || t_arr(v_subscript));
v_subscript := t_arr.next(v_subscript);
end loop;
where v_subscript is a variable of the same datatype of the index by part.
also with a nested table, you can populate the array quickly with:
declare
type myarr is table of number;
t_arr myarr;
v_subscript number;
begin
t_arr := myarr(1, 12, 44);
whereas with an index by array you'd have to have three lines there to populate it:
t_arr(1):= 1;
t_arr(2):= 12;
t_arr(3):= 44;
for your particular case, without the index by is perfectly fine.
further reading: http://docs.oracle.com/cd/E18283_01/appdev.112/e17126/composites.htm

Select from PLSQL Associative array?

Is it possible to use SELECT FROM when using an associative array? I'm passing an array to a stored procedure through a .NET application, and I wanna be able to use that array as a condition when selecting from another table. Lets say I'm passing an array of IDs to the procedure, I wanna be able to do this:
select * from table1 where userID in (select column_value from array)
The type for the array is defined in the package:
type id_array is type of number index by pls_integer
Yes, it is possible, by wrapping the array with a pipelined function. Here's a good primer on pipelined functions:
http://www.oracle-developer.net/display.php?id=429
UPDATE: Oracle 12c now supports querying associative arrays using the TABLE operator, as long as the type is declared in a package spec: https://galobalda.wordpress.com/2014/08/02/new-in-oracle-12c-querying-an-associative-array-in-plsql-programs/
e.g.
select * from table1
where userID in (select column_value from table(array));
No, you can't select from PL/SQL arrays, since you use SQL in select from statements, though you can use DB defined Nested Tables types in SQL. This short article can help you get started.
Take a look a this simple synthetic exmple:
> create type temp_t as table of int;/
Type created.
> select 'test' from dual where 1 in (select * from table(temp_t(1,2,3)));
'TES
----
test
An example using PLSQL (to select from a nested table):
create type temp_r as OBJECT(
temp_varchar2 varchar2(100),
temp_number number(20)
);
/
create type temp_t as TABLE of temp_r;
/
set serveroutput on size 1000000
/
-- PLSQL starts here
declare
temp_rec temp_r := temp_r(null, null); -- empty constructor to initialize object
temp_table temp_t := temp_t(); -- empty constructor to initialize object
lv_ref_cursor SYS_REFCURSOR;
lv_temp_varchar2 varchar(100);
lv_temp_number number(20);
begin
temp_rec.temp_varchar2 := 'first';
temp_rec.temp_number := 1;
temp_table.extend;
temp_table(1) := temp_rec;
temp_table.extend;
temp_table(2) := temp_r('second', 2);
OPEN lv_ref_cursor FOR
SELECT temp_varchar2, temp_number
FROM table(temp_table)
where temp_number = 1;
fetch lv_ref_cursor into lv_temp_varchar2, lv_temp_number;
close lv_ref_cursor;
dbms_output.put_line('returns: ' || lv_temp_varchar2 || ', ' || lv_temp_number);
end;
/

Resources