How can use array type in where clause of update statement - oracle

I want a procedure get a list of disabling privilege, and update their record in table. For doing this scenario, I defined an array as database object with below code:
CREATE OR REPLACE TYPE T_DISABLE_LIST IS TABLE OF NUMBER(32)
Then I defined an input parameter in procedure signature, and got passed value.
PROCEDURE PRC_ROLE_PRIVILAGE_MANAGEMENT(P_REQ_USER_ID IN VARCHAR2,
P_DISABLE_LIST IN T_DISABLE_LIST,
P_RES_DESC OUT VARCHAR2)
BEGIN
UPDATE T_ PRIVILAGE p
SET P.ENABLE_STATUS = 0, P.GRANT_USERID = P_REQ_USER_ID
WHERE P.ID IN (SELECT * FROM TABLE(P_DISABLE_LIST));
COMMIT;
EXCEPTION
WHEN OTHERS THEN
RES_DESC := SUBSTR(SQLERRM,1, 400);
END;
This procedure will be compile successfully. But when I test it, I got this error:
ORA-22905: cannot access rows from a non-nested table item
Any body can help me? And say why this code don't work correctly?
And finally, how can i resolve this problem?
P.S: My orcale version is 9.2!!!

This assumes you are using Oracle 10g or later (and was written before the OP clarified what version they are using)
Use the MEMBER OF operator:
UPDATE T_PRIVILAGE
SET ENABLE_STATUS = 0,
GRANT_USERID = P_REQ_USER_ID
WHERE ID MEMBER OF P_DISABLE_LIST;
You can also use the COLUMN_VALUE pseudo-column:
UPDATE T_PRIVILAGE
SET ENABLE_STATUS = 0,
GRANT_USERID = P_REQ_USER_ID
WHERE ID IN ( SELECT COLUMN_VALUE FROM TABLE( P_DISABLE_LIST ) );
why this code does not work correctly?
SELECT * FROM TABLE( P_DISABLE_LIST )
Is selecting the row from the table. However, the table is generated by a table collection expression and there is no underlying database table to reference a row of so Oracle generates an ORA-22905 exception.(there would be an underlying table if the collection was stored in a nested table; which is why that situation is specifically mentioned in the exception).
Update: PL/SQL solution:
FOR i IN 1 .. P_DISABLE_LIST.COUNT LOOP
UPDATE T_PRIVILAGE
SET ENABLE_STATUS = 0,
GRANT_USERID = P_REQ_USER_ID
WHERE ID = P_DISABLE_LIST(i);
END LOOP;

Related

Oracle access varray elements in SQL

I'm playing around with array support in Oracle and hit a roadblock regarding array access within a SQL query. I'm using the following schema:
create type smallintarray as varray(10) of number(3,0);
create table tbl (
id number(19,0) not null,
the_array smallintarray,
primary key (id)
);
What I would like to do is get the id and the first element i.e. at index 1 of the array. In PostgreSQL I could write select id, the_array[1] from tbl t but I don't see how I could do that with Oracle. I read that array access by index is only possible in PL/SQL, which would be fine if I could return a "decorated cursor" to achieve the same result through JDBC, but I don't know if that's possible.
DECLARE
c1 SYS_REFCURSOR;
varr smallintarray2;
BEGIN
OPEN c1 FOR SELECT t.id, t.THE_ARRAY from tbl t;
-- SELECT t.THE_ARRAY INTO varr FROM table_with_enum_arrays2 t;
-- return a "decorated cursor" with varr(1) at select item position 1
dbms_sql.return_result(c1);
END;
You can do this in plain SQL; it's not pretty, but it does work. You would prefer that Oracle had syntax to hide this from the programmer (and perhaps it does, at least in the most recent versions; I am still stuck at 12.2).
select t.id, q.array_element
from tbl t cross apply
( select column_value as array_element,
rownum as ord
from table(the_array)
) q
where ord = 1
;
EDIT If order of generating the elements through the table operator is a concern, you could do something like this (in Oracle 12.1 and higher; otherwise the function can't be part of the query itself, but it can be defined on its own):
with
function select_element(arr smallintarray, i integer)
return number
as
begin
return arr(i);
end;
select id, select_element(the_array, 1) as the_array_1
from tbl
/
First of all, please don't do that on production. Use tables instead of storing arrays within a table.
Answer to your question is to use column as a table source
SELECT t.id, ta.*
from tbl t,
table(t.THE_ARRAY) ta
order by column_value
-- offset 1 row -- in case if sometime you'll need to skip a row
fetch first 1 row only;
UPD: as for ordering the array I can only say playing with 2asc/desc" parameters provided me with results I've expected - it has been ordered ascending or descending.
UPD2: found a cool link to description of performance issues might happen

ORA-01006: Bind Variable while Executing Select

During the Initialization block of a newly created package, I populate one field in a record stored in an associative array (Indexed by VARCHAR2). I then loop over the associative array to store the second field on each record. These records store a Table Name and the column name which the foreign key for a given table. (Our product has a generic form so I can later generate some SQL with the only difference being the table and column names). Here is some example code:
CREATE OR REPLACE PACKAGE BODY pk_example IS
TYPE pkt_TableInfo IS RECORD
(
BaseTable VARCHAR2(30),
FKColumn VARCHAR2(30)
);
TYPE pkt_TableInfoTable IS TABLE OF pkt_TableInfo INDEX BY VARCHAR2(30);
pk_tTableInfo pkt_TableInfoTable;
pk_sIndex VARCHAR2(30);
/***FUNCTION AND PROCEDURE DEFINITIONS***/
--Initialization Section
BEGIN
pk_tTableInfo('TABLE_A').BaseTable := 'TABLE_B';
pk_tTableInfo('TABLE_C').BaseTable := 'TABLE_D';
pk_sIndex := pk_tTableInfo.FIRST;
WHILE pk_sIndex IS NOT NULL LOOP
SELECT acc.column_name
INTO pk_tTableInfo(pk_sIndex).FKColumn
FROM all_constraints ac
INNER JOIN all_constraints ac2 ON ac.r_owner = ac2.owner AND ac.r_constraint_name = ac2.constraint_name
INNER JOIN all_cons_columns acc ON ac.owner = acc.owner AND ac.constraint_name = acc.constraint_name
WHERE ac.owner = sys_context('userenv', 'current_schema')
AND ac.constraint_type = 'R'
AND ac.table_name = pk_sIndex
AND ac2.table_name = pk_tTableInfo(pk_sIndex).BaseTable;
pk_sIndex := pk_tTableInfo.NEXT(pk_sIndex);
END LOOP;
END pk_example;
Everything compiles just fine, however, whenever I attempt to run a function from this package, I receive an "ORA-01006: bind variable does not exist" error pointing to the line "SELECT acc.column_name".
Now for the kicker. This works in my development environment but is failing when I try to run the package in QA.
I have all the same permissions and I can run the query just fine. In fact, if I replace pk_tTableInfo(pk_sIndex) with a string 'TABLE_B' the query runs just fine. (For the first pass through the loop...then I get No Data)
The foreign keys are the same between environments.
Both environments are running Oracle 12cR2.
Thank you for taking the time to read through this, I appreciate any input.
I've just bumped into the quite same issue. I have FOR..LOOP cursor and I'm getting ORA-01006 error on FOR line. Query is not dynamic. It was working perfectly on Oracle 11g but we upgraded Oracle to 19c version and this issue occured.
The only suspicious thing was that query used function in WHERE clause. Luckily it didn't reference any columns so I removed it from query and calculated before FOR..LOOP statement into a variable. After that it worked like magic.
In your case I would try to get rid of references to collection and use just scalar variables and then assign them to collection elements. The same goes for pk_tTableInfo(pk_sIndex).BaseTable value.

Function results column names to be used in select statement

I have function which returns column names and i am trying to use the column name as part of my select statement, but my results are coming as column name instead of values
FUNCTION returning column name:
get_col_name(input1, input2)
Can И use this query to the results of the column from table -
SELECT GET_COL_NAME(input1,input2) FROM TABLE;
There are a few ways to run dynamic SQL directly inside a SQL statement. These techniques should be avoided since they are usually complicated, slow, and buggy. Before you do this try to find another way to solve the problem.
The below solution uses DBMS_XMLGEN.GETXML to produce XML from a dynamically created SQL statement, and then uses XML table processing to extract the value.
This is the simplest way to run dynamic SQL in SQL, and it only requires built-in packages. The main limitation is that the number and type of columns is still fixed. If you need a function that returns an unknown number of columns you'll need something more powerful, like the open source program Method4. But that level of dynamic code gets even more difficult and should only be used after careful consideration.
Sample schema
--drop table table1;
create table table1(a number, b number);
insert into table1 values(1, 2);
commit;
Function that returns column name
create or replace function get_col_name(input1 number, input2 number) return varchar2 is
begin
if input1 = 0 then
return 'a';
else
return 'b';
end if;
end;
/
Sample query and result
select dynamic_column
from
(
select xmltype(dbms_xmlgen.getxml('
select '||get_col_name(0,0)||' dynamic_column from table1'
)) xml_results
from dual
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns dynamic_column varchar2(4000) path 'DYNAMIC_COLUMN'
);
DYNAMIC_COLUMN
--------------
1
If you change the inputs to the function the new value is 2 from column B. Use this SQL Fiddle to test the code.

Update or insert based on if employee exist in table

Do want to create Stored procc which updates or inserts into table based on the condition if current line does not exist in table?
This is what I have come up with so far:
PROCEDURE SP_UPDATE_EMPLOYEE
(
SSN VARCHAR2,
NAME VARCHAR2
)
AS
BEGIN
IF EXISTS(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN)
--what ? just carry on to else
ELSE
INSERT INTO pb_mifid (ssn, NAME)
VALUES (SSN, NAME);
END;
Is this the way to achieve this?
This is quite a common pattern. Depending on what version of Oracle you are running, you could use the merge statement (I am not sure what version it appeared in).
create table test_merge (id integer, c2 varchar2(255));
create unique index test_merge_idx1 on test_merge(id);
merge into test_merge t
using (select 1 id, 'foobar' c2 from dual) s
on (t.id = s.id)
when matched then update set c2 = s.c2
when not matched then insert (id, c2)
values (s.id, s.c2);
Merge is intended to merge data from a source table, but you can fake it for individual rows by selecting the data from dual.
If you cannot use merge, then optimize for the most common case. Will the proc usually not find a record and need to insert it, or will it usually need to update an existing record?
If inserting will be most common, code such as the following is probably best:
begin
insert into t (columns)
values ()
exception
when dup_val_on_index then
update t set cols = values
end;
If update is the most common, then turn the procedure around:
begin
update t set cols = values;
if sql%rowcount = 0 then
-- nothing was updated, so the record doesn't exist, insert it.
insert into t (columns)
values ();
end if;
end;
You should not issue a select to check for the row and make the decision based on the result - that means you will always need to run two SQL statements, when you can get away with one most of the time (or always if you use merge). The less SQL statements you use, the better your code will perform.
BEGIN
INSERT INTO pb_mifid (ssn, NAME)
select SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN);
END;
UPDATE:
Attention, you should name your parameter p_ssn(distinguish to the column SSN ), and the query become:
INSERT INTO pb_mifid (ssn, NAME)
select P_SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = P_SSN);
because this allways exists:
SELECT * FROM tblEMPLOYEE a where a.ssn = SSN

PL/SQL procedure - too many values

I'm sure this is something simple, but I'm really new to PL/SQL and this has me stuck.
I've written a simple stored procedure to return a few values about a customer. Right off the bat, the %rowtype's are not coming up as reserved keywords but the compiler isn't flagging those as errors.
It is, however, ignoring the entire SQL statement flagging the line FROM demo_customers as too many values. Even if I try reducing it to only select one column it still gives me the same error.
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT customer_id, cust_first_name, cust_last_name, cust_email
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you want to select into a %ROWTYPE record, you'll want to do a SELECT * rather than selecting individual columns
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT *
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you select 4 columns explicitly, Oracle expects you to have 4 variables to select those values into.

Resources