dbms_sql.number_table in IN statement - oracle

I have declared a number table like:
v_areas_hijas dbms_sql.number_table;
I fill the table and then I'm trying this comparation:
select
idarea
from areas
where
IDAREAPADRE in v_areas_hijas;
but I'm getting:
Error(45,22): PLS-00382: expression is of wrong type
How should I do that IN statement?
EDIT 1: using "member of"
select
idarea
from areas
where
IDAREAPADRE member of v_areas_hijas;
I've got this two errors:
Error(45,29): PLS-00382:expression is of wrong type
Error(45,29): PL/SQL: ORA-00932: inconsistent datatypes expected UDT got CHAR
in the table AREAS, IDAREA and IDAREAPADRE are NUMBER,

As in your other question, v_areas_hijas is not a table. If it's a collection type defined in SQL you can use the member of collection operator:
select idarea bulk collect into v_areas_hijas_tmp
from areas
where idareapadre member of v_areas_hijas;
For example, using the predefined type sys.dbms_debug_vc2coll (look inall_coll_types for more examples),
declare
t_demo sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('X','Y','Z');
l_result integer;
begin
select count(*) into l_result
from dual
where dummy member of t_demo;
-- Or this:
select count(*) into l_result
from dual
where dummy in
( select column_value from table(t_demo) );
-- Or this:
select count(*) into l_result
from dual d
join table(t_demo) t on t.column_value = d.dummy;
dbms_output.put_line(l_result);
end;
You can use the table() operator on nested table and varray types declared in packages:
create or replace package demo
as
type demo_collection_type is table of varchar2(1);
end demo;
then
declare
t_demo demo.demo_collection_type := demo.demo_collection_type('X','Y','Z');
l_result integer;
begin
select count(*) into l_result
from dual
where dummy in
( select column_value from table(t_demo) t );
dbms_output.put_line(l_result);
-- Or:
select count(*) into l_result
from dual d
join table(t_demo) t on t.column_value = d.dummy;
dbms_output.put_line(l_result);
end;
As of 12.1 you can't use member of for collection types declared in PL/SQL packages.
Some of these restrictions are lifted in 12.2.

Related

How to assign multiple values to a variable using into clause in oracle query?

I have an oracle stored procedure like this
CREATE OR REPLACE PROCEDURE DEMO (V_IN CHAR, V_OUT VARCHAR2)
IS
BEGIN
FOR ITEM IN LOOP (SELECT DISTINCT (NAME)
FROM TABLE1 INTO V_OUT
WHERE ID = V_IN
LOOP
--CODE TO PRINT V_OUT
END LOOP;
END;
Now how should I create that V_OUT variable so that it can hold all the values coming from query? I'm doing this in oracle12C.
You don't put the INTO clause in the cursor query. And even if you did, you have it in the wrong place in the SQL statement.
You deal with that when you fetch a row from the query:
CREATE OR REPLACE PROCEDURE
DEMO (V_IN CHAR, V_OUT
VARCHAR2)IS
BEGIN
FOR ITEM IN LOOP
(SELECT DISTINCT (NAME)
FROM TABLE1
WHERE ID= V_IN
)
LOOP
dbms_output.put_line(item.name);
v_out := item.name;
END LOOP;
END;
But then the problem is that we just keep overlaying the previous value, so that when your procedure actually exits, the only value of v_out is that last assigned. If you truely need a collection of values, you need to declare your output variable to be a ref cursor, and adjust code accordingly. I've never actually worked with them, so perhaps someone else will chime in.
You can work with collections, like this:
--declare the pakage type
CREATE OR REPLACE PACKAGE PKG_TYPES AS
TYPE LIST_VARCHAR IS TABLE OF VARCHAR2(2000);
END;
--create the proc that will assemble the value list
CREATE OR REPLACE PROCEDURE DEMO ( V_IN IN varchar2, V_OUT IN OUT PKG_TYPES.LIST_VARCHAR) IS
BEGIN
FOR ITEM IN (
SELECT DISTINCT (NAME) name
FROM (SELECT 'X' ID, 'A' name FROM dual
UNION
SELECT 'X' ID, 'b' name FROM dual
UNION
SELECT 'y' ID, 'c' name FROM dual
) TABLE1
WHERE ID= V_IN
)
LOOP
V_OUT.EXTEND;
V_OUT(V_OUT.LAST) := item.name;
--CODE TO PRINT V_OUT
END LOOP;
END;
--use the list. I separated this step but it can be in the demo proc as well
DECLARE
names PKG_TYPES.LIST_VARCHAR := PKG_TYPES.LIST_VARCHAR();
BEGIN
demo('X',names) ;
FOR i IN names.first..names.last LOOP
Dbms_Output.put_line(i);
END LOOP;
END;
You will have to handle exceptions for when no value is returned from the cursor (when no ID is found).
If you need a collection variable - you can use a nested table and bulk collect like below.
To be able to return the value from the procedure you will need to declare the nested table type in some package or on DB schema level.
declare
type test_type is table of varchar2(2000);
test_collection test_type;
begin
select distinct(name) bulk collect into test_collection
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1;
for i in test_collection.first..test_collection.last loop
dbms_output.put_line(test_collection(i));
end loop;
end;
/
If you just need a string with concatenated values - you can use listagg to create it like below
declare
test_str varchar2(4000);
begin
select listagg(name, ', ') within group(order by 1)
into test_str
from (
select distinct name
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1
);
dbms_output.put_line(test_str);
end;
/

Get list of columns and alias name by a query string in Oracle

I'm using Pl/SQL with Oracle Database 11g.
I want to get a list of columns with alias by a query string.
There is a way to get all the column names of a query, using dbms_sql.describe_columns2 but only I get alias.
For example:
DECLARE
l_cursor NUMBER := dbms_sql.open_cursor;
l_ignore NUMBER;
l_desc dbms_sql.desc_tab2;
l_cnt NUMBER;
BEGIN
dbms_sql.parse( l_cursor, 'select a.col1 column1, a.col2 column2 from table_test a', dbms_sql.native );
dbms_sql.describe_columns2( l_cursor, l_cnt, l_desc );
FOR i IN 1 .. l_cnt LOOP
dbms_output.put_line(l_desc(i).col_name);
END LOOP;
dbms_sql.close_cursor( l_cursor );
END;
/
Returns:
column1
column2
Is there any way to get the values a.col1, or a.col2 with alias in the query?
The column names used in a SQL statement can be retrieved using Rob van Wijk's custom view DBA_DEPENDENCY_COLUMNS.
Install the view as SYS and grant select on dba_dependency_columns to <your user>;.
The view only works on objects. Create a VIEW on the SELECT statement and then the dependencies will be available. For example:
create table table_test(col1 number, col2 number);
create or replace view test_view as
select a.col1 column1, a.col2 column2 from table_test a;
select referenced_column
from sys.dba_dependency_columns
where owner = user
and name = 'TEST_VIEW'
order by 1;
Results:
REFERENCED_COLUMN
-----------------
COL1
COL2
The above results get the "list of columns". But part of your answer also implies you may want to get the "column expressions". That would be a completely different task, but is possible. Parsing SQL can be ridiculously difficult so it might help to explain exactly what you want and why you want it.

ORA-00932 facing while compiling the oracle function

I have made below code.
CREATE OR REPLACE TYPE CAL IS OBJECT(
EMPLOYEE_NAME VARCHAR2(30),
R_DATE DATE,
COMMENTS VARCHAR2(50)
);
CREATE OR REPLACE TYPE T_REC is table of cal;
create or replace function CALENDAR(v_team_name varchar2)
return t_rec
IS
v_rec t_rec;
v_COMM VARCHAR2(50);
v_name VARCHAR2(30);
BEGIN
FOR i in (Select ID,EMPLOYEE_NAME from EMPLOYEE where TEAM_NAME=v_team_name)
LOOP
v_name:=i.EMPLOYEE_NAME;
FOR k in (select EMP_ID, START_DT,END_DT-START_DT+1 DAYS,NAME,COMMENTS from EMP_ROTA where EMP_ID=i.ID)
LOOP
v_COMM:=k.NAME||', '||k.COMMENTS;
select t_rec(v_name,k.START_DT+level-1,v_COMM)
into v_rec
from dual connect by level < k.DAYS;
END LOOP;
END LOOP;
Return v_rec;
END;
Facing below error in compiling the function.
Compilation failed,line 14 (04:33:54)
PL/SQL: ORA-00932: inconsistent datatypes: expected UDT got CHARCompilation failed,line 14 (04:33:54)
PL/SQL: SQL Statement ignored
While using below query in function
select v_name,k.START_DT + level -1,v_COMM
into v_REC
from dual connect by level < k.DAYS;
Facing below error
PL/SQL: ORA-00947: not enough valuesCompilation failed,line 14 (04:50:49)
Trying small snippet of code stil facing ORA-00932.
create or replace function ROTA_CALENDAR
return t_CAL
IS
v_CAL t_CAL;
BEGIN
select t_cal('v_name',SYSDATE,'NAME')
into v_CAL
from dual;
Return v_CAL;
END;
what am I doing wrong?
I hope below snippet will help. Couple of things to take in consideration.
1 Don't use "" while any nomenclature
2 You have to select from Object type in the SELECT query as mentioned below.
3 Use BULK COLLECT instead of INTO only...
CREATE OR REPLACE
FUNCTION CALENDAR(
V_TEAM_NAME VARCHAR2)
RETURN t_rec
IS
v_rec t_rec;
v_COMM VARCHAR2(50);
v_name VARCHAR2(30);
BEGIN
FOR i IN
(
SELECT
EMPNO,
'AVRAJIT' ENAME
FROM
EMP
WHERE
JOB=v_team_name
)
LOOP
v_name:=i.ENAME;
FOR k IN
(
SELECT
EMPNO,
SYSDATE,
SYSDATE+1 DAYS,
ENAME,
JOB
FROM
EMP_V1
WHERE
EMPNO=i.EMPNO
)
LOOP
v_COMM:=k.ENAME||', '||k.JOB;
SELECT
CAL(V_NAME,SYSDATE+1,V_COMM) BULK COLLECT
INTO
v_rec
FROM
dual
CONNECT BY level < 10;
END LOOP;
END LOOP;
RETURN v_rec;
END;
-------------------------------OUTPUT-------------------------------------------
SELECT OBJECT_NAME,STATUS FROM ALL_OBJECTS
WHERE OBJECT_NAME = 'CALENDAR';
OBJECT_NAME STATUS
CALENDAR VALID
-------------------------------OUTPUT-------------------------------------------

PL/SQL: declare the list of numbers to be used in IN condition

Is it possible to define the list of ids that will be used in the cursor select?
I've tried to do it as following
DECLARE
insert_user_id number := 1;
type nt_type is table of number;
building_num nt_type := nt_type (1,2,3,4,5);
cursor curs1 is
(
select ID
from objects
where BUILDING_NUM in (building_num)
);
But what I'm getting is the following error:
PLS-00642: local collection types not allowed in SQL statements
What I found out, is that if I'll declare the list of numbers this way, it will be possible to Loop through them. But i don't want it. All I want is to inside of the IN condition of the cursor.
How can I do it?
I you want to ask me, why I just don't put the ids inside of the IN in cursor? My answer is: I have several cursor that use the same list ids.
EDIT:
According to answers below, the code looks as following:
create type nt_type is table of number;
DECLARE
insert_user_id number := 1;
building_num nt_type := nt_type (1,2,3,4,5);
cursor curs1(building_nums nt_type) is
(
select ID
from objects
where BUILDING_NUM in (select * from table(building_nums))
);
1) Sql allowed to use only sql level collections. You have to create it.
create type nt_type is table of number;
2) And query shoull look like
DECLARE
building_num nt_type := nt_type (1,2,3,4,5);
begin
for rec in (select 1 from dual where 1 member of building_num) loop
null;
end loop;
end ;
DECLARE
building_num nt_type := nt_type (1,2,3,4,5);
begin
for rec in (select 1 from dual where 1 in (select column_value from table(building_num)) loop
null;
end loop;
end ;
Also you can check your database for existing collection of number and use it.select * from ALL_COLL_TYPES where coll_type = 'TABLE' and elem_type_name = 'NUMBER'
The root problem is that SQL query runs in SQL context and have no access to the private PL/SQL type type nt_type is table of number; defined in anonymous PL/SQL block. Instead you have to use SQL type. Below you'll find an example how to pass a list of numbers to a cursor. I'm positive you can adapt the idea to your problem !
create table so56_t (
id number
,d varchar2(1)
);
insert into so56_t values(1, 'A');
insert into so56_t values(2, 'B');
insert into so56_t values(3, 'C');
insert into so56_t values(4, 'D');
-- SQL type required (PL/SQL type won't work)
create type num_list_t is table of number;
/
declare
cursor cur_c(p_ids num_list_t) is
select * from so56_t where id in (select* from table(p_ids));
begin
declare
v_foos constant num_list_t := num_list_t(1, 3);
v_bars constant num_list_t := num_list_t(2, 4);
v_r cur_c%rowtype;
begin
open cur_c(v_foos);
loop
fetch cur_c into v_r;
exit when cur_c%notfound;
dbms_output.put_line(v_r.d);
end loop;
close cur_c;
open cur_c(v_bars);
loop
fetch cur_c into v_r;
exit when cur_c%notfound;
dbms_output.put_line(v_r.d);
end loop;
close cur_c;
end;
end;
/
Example run
SQL> /
A
C
B
D
PL/SQL procedure successfully completed.
SQL>
If you want to use string and not table of number
DECLARE
insert_user_id number := 1;
building_nums varchar2(100) := '1,2,3,4,5';
cursor curs1 is
(
select ID
from objects
where BUILDING_NUM in (
SELECT to_number(REGEXP_SUBSTR (building_nums , '[^,]+', 1, LEVEL))
FROM DUAL
CONNECT BY REGEXP_SUBSTR (building_nums , '[^,]+', 1, LEVEL) IS NOT NULL
)
);
And variation of #Arkadiusz Ɓukasiewicz answer
DECLARE
insert_user_id NUMBER := 1;
-- type nt_type is table of number;
svar VARCHAR2 (100) := '1,2,3,4,5';
building_nums nt_type;
n NUMBER;
CURSOR curs1
IS
(SELECT object_ID
FROM all_objects
WHERE object_id IN (SELECT COLUMN_VALUE
FROM TABLE (building_nums)));
BEGIN
SELECT TO_NUMBER (REGEXP_SUBSTR (svar, '[^,]+', 1, LEVEL))
BULK COLLECT INTO building_nums
FROM DUAL
CONNECT BY REGEXP_SUBSTR (svar, '[^,]+', 1, LEVEL) IS NOT NULL;
OPEN curs1;
LOOP
FETCH curs1 INTO n;
EXIT WHEN curs1%NOTFOUND;
dbms_output.put_line (n);
END LOOP;
CLOSE curs1;
END;
You can use the below query by replacing '1,2,3,4,5' with required list of values or fetch from table.
SELECT REGEXP_SUBSTR(('1,2,3,4,5'),'[^,]+', 1, LEVEL) FROM DUAL CONNECT BY REGEXP_SUBSTR(('1,2,3,4,5'), '[^,]+', 1, LEVEL) IS NOT NULL;
This is similar to what you have already, except that I'm not declaring a cursor. I just wanted to post it in case it's helpful to someone.
As already mentioned, creating the table type (nt_type) first allows it to be used as desired without throwing the error:
PLS-00642: local collection types not allowed in SQL statements
With that said, here is yet another working example...
GIVEN: a table called OBJECTS containing the following data:
ID BUILDING_NUM
---- ------------
1000 1
2000 2
3000 3
4000 4
5000 5
6000 6
7000 7
8000 8
9000 9
... and a type called nt_type
create type nt_type is table of number;
The PLSQL below loops through IDs in the OBJECTS table where BUILDING_NUM matches any in a given set (e.g., 1,2,3,4,5) and outputs some text to screen. Of course it could do something more useful like call a procedure, or execute some SQL.
set serveroutput ON
DECLARE
building_nums nt_type := nt_type (1,2,3,4,5);
BEGIN
for i in (
select * from objects
where BUILDING_NUM in (select column_value from table(building_nums))
) LOOP
dbms_output.put_line('--do something with ID: '||i.ID||', matching building_num: '||i.BUILDING_NUM||';');
END LOOP;
END;
/
Output:
--do something with ID: 1000, matching building_num: 1;
--do something with ID: 2000, matching building_num: 2;
--do something with ID: 3000, matching building_num: 3;
--do something with ID: 4000, matching building_num: 4;
--do something with ID: 5000, matching building_num: 5;
PL/SQL procedure successfully completed.

PL/SQL - Use "List" Variable in Where In Clause

In PL/SQL, how do I declare variable MyListOfValues that contains multiple values (MyValue1, MyValue2, etc.)
SELECT *
FROM DatabaseTable
WHERE DatabaseTable.Field in MyListOfValues
I am using Oracle SQL Developer
Create the SQL type like this:
CREATE TYPE MyListOfValuesType AS TABLE OF VARCHAR2(4000);
And then use it in a SQL statement
DECLARE
MyListOfValues MyListOfValuesType;
BEGIN
MyListOfValues := MyListOfValuesType('MyValue1', 'MyValue2');
FOR rec IN (
SELECT *
FROM DatabaseTable
WHERE DatabaseTable.Field in (
SELECT * FROM TABLE(MyListOfValues)
)
)
LOOP
...
END LOOP;
END;
Up until Oracle 11g, this only works with a SQL TABLE type, not with a PL/SQL TABLE type. With Oracle 12c, you could also use PL/SQL types.
Use a collection:
CREATE TYPE Varchar2TableType AS TABLE OF VARCHAR2(200);
Or use a built-in type like SYS.ODCIVARCHAR2LIST or SYS.ODCINUMBERLIST:
VARIABLE cursor REFCURSOR;
DECLARE
your_collection SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
BEGIN
your_collection.EXTEND( 100 );
your_collection( 1) := 'Some value';
your_collection( 2) := 'Some other value';
-- ...
your_collection(100) := DBMS_RANDOM.STRING( 'x', 20 );
OPEN :cursor FOR
SELECT t.*
FROM your_table t
INNER JOIN
TABLE( your_collection ) c
ON t.id = c.COLUMN_VALUE;
END;
/
PRINT cursor;
How about using a WITH clause which basically builds a temp table? Not real reusable. You could use an array or I would argue joining to a lookup table would be better.
WITH MyListOfValues(col1) AS (
select 'MyValue1' from dual union
select 'MyValue2' from dual union
select 'MyValue3' from dual
)
SELECT *
FROM DatabaseTable
WHERE Column in (
select col1
from MyListOfValues);

Resources