Select count from collection - oracle

Is there a way to capture the count in a collection table for a certain condition.
For example :
SELECT COUNT(*)
BULK COLLECT INTO v_cnt
FROM tt_product
WHERE region = 'NA';
The above query throws error saying:
Error(649,13): PL/SQL: ORA-00942: table or view does not exist
Any insights would be highly appreciated .
Thanks

Firstly, remove BULK COLLECT, you are getting a single value...
Secondly, if I understand you right, and you are talking about the collection of e.g. type
CREATE OR REPLACE T AS TABLE OF ...
then wrap the name with the table() keyword
SELECT COUNT(*)
INTO v_cnt
FROM table(tt_product)
WHERE region = 'NA';

Dump your results into a #TEMP table first then do your count from there.
Something like:
SELECT * BULK COLLECT INTO #TEMP
FROM tt_product
Where region = 'NA'
SELECT COUNT(*) FROM #TEMP
This is untested so you might have to play with it a little to get it to work.
My example is for SQL Server, you'll have to find the equivalent for ORACLE as I'm not as familiar with it.

declare
b l_number := new l_number(1,2,3,4); -- type L_number is table of number ;
v1 number;
v2 number;
begin
select count(*) into v1 from table(b);
select CARDINALITY(b) into v2 from dual;
dbms_output.put_line(v1);
dbms_output.put_line(v2);
end;

Related

PL/SQL Table type compilation error

I got table types:
CREATE OR REPLACE TYPE "TABLE_OF_VARCHAR2" AS TABLE OF VARCHAR2(4000);
CREATE OR REPLACE TYPE "TABLE_OF_NUMBER" AS TABLE OF NUMBER;
And in my package body i got a cursor:
cursor c_src_m(trans_list table_of_number, v_codes table_of_varchar2, ...) is
select ....something
where ....
--AND ta.id in (select COLUMN_VALUE from TABLE(trans_list))
--AND (tb.UNDERLYING_VALUE in (select COLUMN_VALUE from TABLE(v_codes)) OR (v_codes is null or (select count(1) from TABLE(v_codes)) = 0))
For each of last 2 lines if i uncomment them i get an error:
ORA-22905: cannot access rows from a non-nested table item
*Cause: attempt to access rows of an item whose type is not known at
parse time or that is not of a nested table type
*Action: use CAST to cast the item to a nested table type
I looked over 3 hours for a solution and still couldn't find a working one. Do i really need to cast it as error message says? Does anyone know what is the problem here?
There might be something wrong in your code which you are either not showing in your question or have missed it. Please see the below DEMO which is on the similar lines of your question. I used it the same way you did and its working fine on Oracle11g. Also, you can use MEMBER OF as well inplace of your Select statement query in where clause. See below and read my inline comments to understand more.
Table and Types Creation:
CREATE OR REPLACE TYPE TABLE_OF_VARCHAR2 AS TABLE OF VARCHAR2(4000);
/
CREATE OR REPLACE TYPE TABLE_OF_NUMBER AS TABLE OF NUMBER;
/
Create table Num_varchar(col1 number, col2 varchar2(1000));
/
INSERT ALL
INTO Num_varchar VALUES (1,'A')
INTO Num_varchar VALUES (2,'B')
INTO Num_varchar VALUES (3,'C')
INTO Num_varchar VALUES (4,'D')
INTO Num_varchar VALUES (5,'E')
SELECT * FROM dual;
/
Block:
DECLARE
CURSOR c_src_m(trans_list table_of_number, v_codes table_of_varchar2)
IS
SELECT col1,
col2
FROM Num_varchar
--Inplace of select query you can use Member of as well. However if you want to use select query, this will also work.
--WHERE col1 IN (SELECT COLUMN_VALUE FROM TABLE(trans_list) )
--AND col2 IN (SELECT COLUMN_VALUE FROM TABLE(v_codes));
WHERE col1 MEMBER OF trans_list
and col2 MEMBER OF v_codes;
--Populated the collection so that i can use it in my query above
var_varchr2 TABLE_OF_VARCHAR2:=TABLE_OF_VARCHAR2('A','B','C','D');
var_number TABLE_OF_NUMBER :=TABLE_OF_NUMBER(1,2,3,4);
var1 NUMBER;
var2 VARCHAR2(100);
BEGIN
OPEN c_src_m ( var_number , var_varchr2);
LOOP
FETCH c_src_m INTO var1,var2;
EXIT WHEN c_src_m%NOTFOUND;
--dispalying the result of the cursor
dbms_output.put_line(var1 || var2);
END LOOP;
Close c_src_m;
END;
Output:
1A
2B
3C
4D
Note: If you had create the types in Package specification, it will not work. Until Oracle11g , any types created under PLSQL scope cannot be referred in SQL statement inside the PLSQL block. If you have done so, just create types out of package scope and it should work then.

ORA-22905 - While using DBMS_SQL.Number_Table for Table Operator

All,
Here is a simplified version of what I am attempting to accomplish. Instead of declaring my own type, I used the Number_Table type from the dbms_sql package.
First I created a simple test table:
CREATE TABLE collect_test(id NUMBER(38), other_info VARCHAR2(5));
Then, populated the table with a small amount of data:
INSERT INTO collect_test
SELECT rownum, chr(rownum+60)
FROM dual
CONNECT BY rownum <= 10;
Finally, I attempt to use PL/SQL to select some rows into a collection then use that collection to delete rows from the table:
DECLARE
l_tIDList DBMS_SQL.Number_Table;
BEGIN
SELECT ct.id
BULK COLLECT INTO l_tIDList
FROM collect_test ct
WHERE mod(ct.id, 2) = 0;
DELETE FROM (SELECT ct.id
FROM collect_test ct
INNER JOIN table(l_tIDList) ids ON ct.id = ids.column_value);
ROLLBACK;
END;
/
However, when I run this PL/SQL block I receive these errors:
ORA-06550: line 11, column 33:
PLS-00382: expression is of wrong type
ORA-06550: line 11, column 27:
PL/SQL: ORA-22905: cannot access rows from a non-nested table item
ORA-06550: line 9, column 3: PL/SQL: SQL Statement ignored
In all the other questions/articles I have found it seems like the coder was either trying to use a local type or forgetting to BULK COLLECT. I appreciate any suggestions you may have.
N.B.: I realize that I can do this specific functionality using a single DELETE statement. My actual scenario is more complicated and can't be done with a single statement.
Although it is not clear what you are trying to accomplish, there are two things which are wrong with your pl/sql block.
You can use the TABLE() function on the variables of collection
types defined in the schema and not on those defined locally
The way you are running your DML ( delete ) statement is wrong, it throws the error `
ORA-22841: DML on PL/SQL collections not supported.
So, create a collection TYPE in your schema and declare a collection of that type in your PL/SQL block and for your delete use an IN condition
CREATE OR REPLACE TYPE idlisttable as TABLE OF NUMBER;
DECLARE l_tidlist idlisttable;
BEGIN
SELECT ct.id BULK collect
INTO l_tidlist
FROM collect_test ct
WHERE MOD(ct.id, 2) = 0;
DELETE
FROM collect_test ct
WHERE ct.id IN (
SELECT ids.column_value
FROM TABLE (l_tidlist) ids
);
ROLLBACK;
END;
/
`
I have tried this using a cursor and it worked for me. Please try as below.
DECLARE
l_tIDList DBMS_SQL.Number_Table;
CURSOR c
IS
SELECT ct.id FROM collect_test ct WHERE mod(ct.id, 2) = 0;
BEGIN
OPEN c;
LOOP
FETCH c bulk collect INTO l_tIDList;
forall i IN 1 .. l_tIDList.count
DELETE FROM collect_test t WHERE t.id = l_tIDList(i);
EXIT
WHEN c%notfound;
END LOOP;
COMMIT;
CLOSE c;
END ;
/

RowSet Returns From Inter-active queries

New to Oracle, having loads of niggling troubles, strong in tSQL not in pl\SQL
I want to list the contents of a table in SQL Developer dependant on some pre-programmed variables, ultimately I would like this to be like a MS-SQL Stored Procedure where I call it passing parameters...
I get error ...an INTO clause is expected in this SELECT statement
TIA - Andy
DECLARE
tbl_name varchar2(30) := 'ALL';
ix_name varchar2(30) := 'ALL';
BEGIN
SELECT table_name, index_name, monitoring
FROM v$object_usage
WHERE (table_name = UPPER(tbl_name) or UPPER(tbl_name) = 'ALL')
AND (index_name = UPPER(ix_name) or UPPER(ix_name) = 'ALL');
END;
You get this error because your select is fetching some data but you're not storing it into any variable.
Your select should be like,
SELECT table_name, index_name, monitoring
into var1,var2,var3
FROM v$object_usage
But if your select returns more than one rows this will throw an error, so you might have to use collections in that case.

How do you select into a nested type in oracle pl/sql?

I want to be able to delete by rowid then immediately insert the data being deleted in an audit table.
There are far too many records to
INSERT INTO ... SELECT CRITERIA then DELETE ... CRITERIA.
I already know how to do everything just using rowid and INSERT INTO ... SELECT.
Inside package body:
TYPE some_type IS RECORD (
row_id ROWID,
full_row table_name%ROWTYPE
);
TYPE some_type_list IS TABLE OF some_type
INDEX BY BINARY_INTEGER;
PROCEDURE do_stuff
IS
lc_data SYS_REFCURSOR;
lt_recs some_type_list;
BEGIN
OPEN lc_date FOR
SELECT rowid, a.*
FROM table_name;
LOOP
FETCH lc_data
BULK COLLECT INTO lt_recs
LIMIT 50000;
EXIT WHEN lt_recs.COUNT = 0;
--
FORALL i IN lt_recs.FIRST..lt_recs.LAST
DELETE table_name
WHERE ROWID = lt_recs(i).row_id;
--
FORALL i IN lt_recs.FIRST..lt_recs.LAST
INSERT INTO table_name_audit VALUES lt_recs(i).full_row;
END LOOP;
END;
If I try that i get the following error:
Line: 117 Column: 25 Type: error Text: PLS-00597: expression 'LT_RECS' in the INTO list is of wrong type
Oracle versions prior to 11gR2 restrict us to use BULK COLLECT into a collection (nested table or varray) of records. Read more here on Oracle Docs.
If you want to see how it is done in 11gR2, scroll down to EDIT 2 section of this answer.
An alternative tho this can be the use of separate collections for every column- an approach that is most widely used. In this you can have:
/*
TYPE some_type IS RECORD (
row_id ROWID,
full_row table_name%ROWTYPE
);
TYPE some_type_list IS TABLE OF some_type
INDEX BY BINARY_INTEGER;
-- */
CREATE TYPE t_row_id IS TABLE OF ROWID;
CREATE TYPE t_col1 IS TABLE OF table_name.col1%TYPE;
CREATE TYPE t_col2 IS TABLE OF table_name.col2%TYPE;
CREATE TYPE t_col3 IS TABLE OF table_name.col3%TYPE;
...
...
CREATE TYPE t_colN IS TABLE OF table_name.colN%TYPE;
PROCEDURE do_stuff
IS
lc_data SYS_REFCURSOR;
-- lt_recs some_type_list;
row_id t_row_id;
col1 t_col1;
col2 t_col2;
col3 t_col3;
...
...
colN t_colN;
BEGIN
OPEN lc_date FOR
SELECT rowid, a.*
FROM table_name;
LOOP
FETCH lc_data
BULK COLLECT INTO row_id, col1, col2, col3, ..., colN
LIMIT 50000;
EXIT WHEN lt_recs.COUNT = 0;
--
FORALL i IN row_id.FIRST..row_id.LAST
DELETE table_name
WHERE ROWID = row_id(i);
--
FORALL i IN col1.FIRST..col1.LAST
INSERT INTO table_name_audit VALUES (col1(i), col2(i), col3(i), ..., colN(i));
END LOOP;
END;
I have not removed many of the rows in your program in order to let you understand the changes.
EDIT : Refer to the "Restrictions on BULK COLLECT" section of the Oracle Docs link I have given above and also here.
EDIT #2 :
You have to use CREATE TYPE ... IS OBJECT instead of RECORD. Also, You need to modify the SELECT statement the way I have done when I tried it. Please see the Oracle Docs here and a StackOverflow question here for further reference.
The code I tried on my machine (runs Oracle 11g R2) is as follows:
-- SELECT * FROM user_objects WHERE object_type = 'TYPE';
CLEAR SCREEN;
SET SERVEROUTPUT ON;
CREATE OR REPLACE TYPE temp_t_test AS OBJECT ( -- << OBJECT, not RECORD.
test_id INTEGER
, test_val VARCHAR2(50)
);
/
CREATE OR REPLACE TYPE temp_tbl_test AS TABLE OF TEMP_T_TEST;
/
DECLARE
v_test TEMP_TBL_TEST;
BEGIN
SELECT temp_t_test(t_id, t_val) -- << Notice the syntax
-- I'm selecting the columns as the defined OBJECT type.
BULK COLLECT INTO v_test
FROM (SELECT 1 AS t_id, 'ABCD' AS t_val FROM dual
UNION ALL
SELECT 2, 'WXYZ' FROM dual
UNION ALL
SELECT 3, 'PQRS' FROM dual);
dbms_output.put_line('Bulk Collect Successful!');
END;
/
** OUTPUT **:
TYPE temp_t_test compiled
TYPE temp_tbl_test compiled
anonymous block completed
Bulk Collect Successful!
I don't think that I'd take this approach at all, to be honest.
A faster method would be along the lines of performing a multitable insert:
insert the table columns into the audit table, possibly using direct path (APPEND hint) for efficiency
insert the rowid's into an on-commit-delete-rows global temporary table.
Then perform a delete against the original table using DELETE .. WHERE ROWID IN (SELECT ORIGINAL_ROWID FROM MY_GLOBAL_TEMP_TAB)
... and then commit.
Faster, and less code I think.
What you're trying to works in 11gR2 - what version are you on?.
The only wrong-looking thing in your post is this:
OPEN lc_date FOR
SELECT rowid, a.*
FROM table_name;
It ought to be this ...
OPEN lc_data FOR
SELECT a.rowid, a.*
FROM table_name a;
... but these may simply be typos you introduced when sanitizing your code to post here.

How to use variables in an Oracle PL/SQL where clause

I can't seem to get variables to work in an Oracle PL/SQL where clause. I come from a Microsoft SQL Server background and there it was easy. For example, what would be all steps needed to do something similar to the following?
declare #var int set #var = 1
select * from SomeTable where SomeField = #var
This doesn't seem like it should be hard in PL/SQL, but evidently it is. :-/ I hear I need to use cursors and the like in PL/SQL?
Any help would be greatly appreciated. Thanks.
What do you want to do with the data that the SELECT returns? If you just want to see it you don't need PL/SQL at all, just do this in SQL Plus:
variable var number
exec :var := 1
select * from SomeTable where SomeField = :var;
Or in a tool like SQL Developer or Toad, just do this:
select * from SomeTable where SomeField = :var;
and it will prompt you to enter the value for :var.
The following code declares a variable var to use in the WHERE clause, and a variable result to put the result in then executes it inside a PL/SQL block.
DECLARE
var INT := 1;
result INT;
BEGIN
SELECT 123
INTO result
FROM DUAL
WHERE var = 1;
DBMS_OUTPUT.put_line (var);
DBMS_OUTPUT.put_line (result);
END;
The DBMS_OUTPUT.PUT_LINE calls make it produce this DBMS output:
1
123
declare
type t_rec is record
(
col1 number,
col2 myTable.col2%type
);
v_rec t_rec;
type t_tab is table of v_rec%type index by binary_integer;
v_tab t_tab;
begin
select col1, col2
bulk collect into v_tab
from myTable
where col3 = 'BLAH';
-- do something great with v_tab...
end;
Also know that if you try to select into (or bulk collect into) a variable and no rows are returned, you'll get a no_data_found exception, so you may want to handle that situation.
See more here on pl/sql collections. Above uses an associative array, but there are nested tables and varrays as well. Again, see the link.
Hope that helps.
I use it like this
select * from sec_mainmenu where serno = '&MENU_ID';
When you run it, pl/sql will prompt for MENU_ID value.

Resources