100 strings in IN operator, oracle pl/sql - oracle

I am passing 100 table_names in the IN operator as strings, but I am getting numeric overflow error due to too many operands.
Is there a way where I can use something else besides IN ?
set serveroutput on
DECLARE
...
BEGIN
FOR r IN
(
SELECT table_name, column_name
FROM all_tab_columns
WHERE table_name IN (...100 strings)
)
AND data_type = 'NUMBER'
ORDER BY table_name, column_id
)
LOOP
execute immediate 'SELECT COUNT("' || r.column_name || '")
,COUNT(nvl2("' || r.column_name || '", NULL, 1))
FROM "' || r.table_name || '"'
INTO not_null_count, null_count;
DBMS_OUTPUT.PUT_LINE(..)
Note: For variables I am using PLS_Integer.

The suggested action for ORA-01426 is "reduce the operands". This doesn't mean reduce the number of operands. It means you're trying to put too large a number into a variable. So shrink the number, or enlarge the variable.
You say:
"for variables I am using PLS_Integer"
So, if you have a large table, and by large I mean more than 2,147,483,647 rows, you will get a numeric overflow. Because PLS_INTEGER is a 32-bit data type.
If this is your scenario then you need to declare your variables of data type INTEGER instead (or NUMBER(38,0)).
As #BobJarvis points out, PLS_INTEGER is optimized for hardware arithmetic. So the general advice would be to use it for counting type operations. However, your case simply requires a variable to hold the output of a SQL count() operation, so I don't think there will be any difference in efficiency.

I believe the limit on 'IN' clause is 1000 strings and not 100 strings. To debug:
a.) Try running your implicit cursor query in SQL.
b.) If it works fine then run the query in execute immediate after substituting the column name.
Also , try increasing the size of your not_null_count and null_count variables.
Hope it Helps
Vishad

some other possible solutions
use a temp table - populate it with the table names to filter join to it.
create a global array type
create type table_of_varchar2 is table of varchar2(30)
populate the array and filter using table_name member of arr_tables_list

Is there a way where I can use something else besides IN ?
Consider using a cursor instead.

Related

Can I create a fixed length type in oracle database?

Using Oracle 11gR2, I have a need to create a fixed length string comprised of 200 + fields from a table.
I have created a dynamic select statement that creates this by reading a table that has the relationship between the fixed length string and the database fields. I end up with something like;
select rpad(char_field1, 20,' ') ||
lpad(num_field1,6,'0') ||
rpad(' ',8,' ') AS FIXED_STRING
from my_table
It works fine, but is CPU intensive with all the concatenation and padding etc.
I noticed that there is the ability to create an external table of type fixed, but my data never needs to be written to disk, just passed to a program for processing.
I wondered if there was an equivalent in memory structure similar to;
TYPE MY_RECORD_TYPE IS RECORD
(
CHAR_FIELD1 position(1:20) VARCHAR2(20),
NUM_FIELD2 position(21:6) NUMBER(6),
FILL_FIELD1 position(28,8) VARCHAR2(8)
);
That would allow me to create a string to pass to something else, in my case VB.NET?
My overall goal is to come up with the most efficient way of creating fixed length strings from column data in a table.
Virtual Columns might work for you - hard to tell from the information given. On the table itself you define the virtual column - which will concatenate the fields you are interested in.
ALTER TABLE YOUR_TABLE
ADD (FIXED_STRING char(34) Generated Always as
(rpad(char_field1, 20,' ') || lpad(num_field1,6,'0') || rpad(' ',8,' ') ));
The virtual column can then be indexed, etc...

Performance of using a nested table inside the IN clause - Oracle

I'm trying to use a nested table inside the IN clause in a PL-SQL block.
First, I have defined a TYPE:
CREATE OR REPLACE TYPE VARCHAR_ARRAY AS TABLE OF VARCHAR2(32767);
Here is my PL-SQL block using the 'BULK COLLECT INTO':
DECLARE
COL1 VARCHAR2(50) := '123456789';
N_TBL VARCHAR_ARRAY := VARCHAR_ARRAY();
C NUMBER;
BEGIN
-- Print timestamp
DBMS_OUTPUT.PUT_LINE('START: ' || TO_CHAR(SYSTIMESTAMP ,'dd-mm-yyyy hh24:mi:ss.FF'));
SELECT COLUMN1
BULK COLLECT INTO N_TBL
FROM MY_TABLE
WHERE COLUMN1 = COL1;
SELECT COUNT(COLUMN1)
INTO C
FROM MY_OTHER_TABLE
WHERE COLUMN1 IN (SELECT column_value FROM TABLE(N_TBL));
-- Print timestamp
DBMS_OUTPUT.PUT_LINE('ENDED: ' || TO_CHAR(SYSTIMESTAMP ,'dd-mm-yyyy hh24:mi:ss.FF'));
END;
And the output is:
START: 01-08-2014 12:36:14.997
ENDED: 01-08-2014 12:36:17.554
It takes more than 2.5 seconds (2.557 seconds exactly)
Now, If I replace the nested table by a subquery, like this:
DECLARE
COL1 VARCHAR2(50) := '123456789';
N_TBL VARCHAR_ARRAY := VARCHAR_ARRAY();
C NUMBER;
BEGIN
-- Print timestamp
DBMS_OUTPUT.PUT_LINE('START: ' || TO_CHAR(SYSTIMESTAMP ,'dd-mm-yyyy hh24:mi:ss.FF'));
SELECT COUNT(COLUMN1)
INTO C
FROM MY_OTHER_TABLE
WHERE COLUMN1 IN (
-- Nested table replaced by a subquery
SELECT COLUMN1
FROM MY_TABLE
WHERE COLUMN1 = COL1
);
-- Print timestamp
DBMS_OUTPUT.PUT_LINE('ENDED: ' || TO_CHAR(SYSTIMESTAMP ,'dd-mm-yyyy hh24:mi:ss.FF'));
END;
The output is:
START: 01-08-2014 12:36:08.889
ENDED: 01-08-2014 12:36:08.903
It takes only 14 milliseconds...!!!
What could I do to enhance this PL-SQL block ?
Is there any database configuration needed?
Are the two query plans different?
Assuming that they are, the difference is likely that the optimizer has reasonable estimates about the number of rows the subquery will return and, thus, is able to choose the most efficient plan. When your data is in a nested table (I'd hate to use the word array in the type declaration here since that implies that you're using a varray when you're not), Oracle doesn't have information about how many elements are going to be in the collection. By default, it's going to guess that the collection has as many elements as your data blocks have bytes. So if you have 8k blocks, Oracle will guess that your collection has 8192 elements.
Assuming that your actual query doesn't return anywhere close to 8192 rows and that it actually returns many more or many fewer rows, you can potentially use the cardinality hint to let the optimizer make a more accurate guess. For example, if your query generally returns a few dozen rows, you probably want something like
SELECT COUNT(COLUMN1)
INTO C
FROM MY_OTHER_TABLE
WHERE COLUMN1 IN (SELECT /*+ cardinality(t 50) */ column_value
FROM TABLE(N_TBL) t);
The literal you put in the cardinality hint doesn't need to be particularly accurate, just close to general reality. If the number of rows is completely unknown the dynamic_sampling hint can help.
If you are using Oracle 11g, you may also benefit from cardinality feedback helping the optimizer learn to better estimate the number of elements in a collection.

How to print a field type on oracle without a `SELECT`

Simple question:
How to print a field's type?
DESC TABLE_FOO.FIELD_FOO;
Results printing the whole table description.
How to print a specific field details with a command, i.e, without a SELECT?
There is no built-in way to do this, at least in any of the clients I've used. As mentioned in comments, describe is a client wrapper around a data dictionary query, and each client can implement it differently - SQL*Plus and SQL Developer seem to be slightly different, and no client is actually required to support this command at all.
Just for fun, if you really wanted to you could create a procedure to figure out and format the data type the same as desc, something like:
create or replace procedure col_data_type (p_table_name varchar2,
p_column_name varchar2)
as
l_data_type varchar2(30);
begin
select data_type
|| case when data_type = 'VARCHAR2' then '(' || data_length || ')' end
|| case when data_type = 'NUMBER' and data_precision is not null then
'(' || data_precision
|| case when data_scale is not null and data_scale > 0 then
',' || data_scale end
|| ')' end
into l_data_type
from user_tab_columns
where table_name = p_table_name
and column_name = p_column_name;
dbms_output.put_line(l_data_type);
end col_data_type;
/
Perhaps with more special formatting for other data types, but those are the obvious ones. You can then call that with execute. With a dummy table:
create table t42(i integer, n1 number, n2 number(10), n3 number(10,5),
v varchar2(10), c clob)
Then:
set serveroutput on
exec col_data_type('T42','I');
NUMBER
exec col_data_type('T42','N1');
NUMBER
exec col_data_type('T42','N2');
NUMBER(10)
exec col_data_type('T42','N3');
NUMBER(10,5)
exec col_data_type('T42','V');
VARCHAR2(10)
exec col_data_type('T42','C');
CLOB
Not entirely sure how useful that might be, or why you want to be able to do this at all. Also notice that it requires the client to be retrieving and displaying dbms_output buffer. You could make it a function instead, which puts you back to using a select, albeit a shorter one...
I don't believe it is possible to accomplish this without using a SELECT statement.
DESC OWNER.TABLE_NAME; is basically just running a query like this anyways (although not exactly the same, that depends on your client):
SELECT *
FROM ALL_TAB_COLS
WHERE OWNER = &theOwner
AND TABLE_NAME = &theTable;
If you want to only return a single column, you can do this:
SELECT *
FROM ALL_TAB_COLS
WHERE OWNER = &theOwner
AND TABLE_NAME = &theTable
AND COLUMN_NAME = &theColumn;
As #AlexPoole suggests, you could work around this by writing your own custom PROCEDURE or FUNCTION to return exactly what you need, but I believe the answer to the question "is there a built in command other than SELECT that does exactly what you need" is no, there is not.
I don't think you can do this without using a SELECT. You may be able to come up with some way to route the output of DESC to a file and then parse the file out to get what you want, but honestly - SELECT is going to be much easier.
Relational databases store the description of what they store in the database, where said description can be obtained in the same manner as any other information in the database, i.e. by using a SELECT to read it. The actual tables which store this are somewhat difficult to interpret, but happily Oracle has taken pity on us poor users and provided views which present this info in an easy-to-read manner. To get the type of a field you want to do a select one of the *_TAB_COLS views, where * is either USER, ALL, or DBA. The particular columns of interest are likely going to be COLUMN_NAME, DATA_TYPE, and DATA_LENGTH.
Share and enjoy.

PL/SQL rewrite concatenated query with 'IN' clause

Currently I have in my pl/sql code following statements:
-- vList looks like '1,2,3,4'
vStatement := 'SELECT NAME FROM T_USER WHERE ID IN ( ' || vList || ' ) ';
Execute Immediate vStatement BULK COLLECT INTO tNames;
I think that concatenating of query if bad practice, so I want to make this query without using stings. What is the way to rewrite this ?
P.S. maybe people here can point out why concatenation of queries is bad, because i don't have enough reasons to prove that this style is bad.
my guess is that you took some steps previously to get vList id's into a delimited string (you don't say how vList was populated ). Why not keep as one query?
begin
...
select name
bulk collect into tNames
from t_user
where id in (select id from some_table where ...);
...
Context switching when run many times can be painful, but to me the worst part is that you are blindly accepting parameter input to be a list of numbers, when it could be anything really. It could (innocently) be '1,2,X', and you'll get a runtime error "invalid number". Or worse, it could be a SQL injection attack. Its bad practice in general (dynamic sql does have its place), but definitely NOT how you're using it.
Try something like this:
create or replace type t_num_tab as table of number;
create or replace procedure test_proc(i_list in t_num_tab) as
type t_name_tab is table of varchar2(100);
l_names t_name_tab;
begin
-- get names
select name
bulk collect into l_names
from user_table
where id in (select * from table(i_list));
-- do something with l_names
dbms_output.put_line('Name count: ' || l_names.count);
end;
You can create an object type if you need something more complicated than a list of numbers.
It's not just that concatenation is slow. It's that dynamic queries in plsql are REALLY slow. Here's a good writeup of both the how and why to do this:
Ask Tom: How can I do a variable "in list"

Bulk Insert with large static data in Oracle

I have a simple table with one column of numbers. I want to load about 3000 numbers in it. I want to do that in memory, without using SQL*Loader. I tried
INSERT ALL
INTO t_table (code) VALUES (n1)
INTO t_table (code) VALUES (n2)
...
...
INTO t_table (code) VALUES (n3000)
SELECT * FROM dual
But I fails at 1000 values. What should I do ? Is SQL*Loader the only way ? Can I do LOAD with SQL only ?
Presumably you have an initial value of n. If so, this code will populate code with values n to n+2999 :
insert into t_table (code)
select (&N + level ) - 1
from dual
connect by level <=3000
This query uses a SQL*Plus substitution variable to post the initial value of n. Other clients will need to pass the value in a different way.
"Assume that I am in c++ with a stl::vector, what query should I
write ?"
So when you wrote n3000 what you really meant was n(3000). It's easy enough to use an array in SQL. This example uses one of Oracle's pre-defined collections, a table of type NUMBER:
declare
ids system.number_tbl_type;
begin
insert into t_table (code)
select column_value
from table ( select ids from dual )
;
end;
As for mapping your C++ vector to Oracle types, that's a different question (and one which I can't answer).

Resources