Execute immediate use in select * from in oracle - oracle

I am trying to get maximum length of column data in oracle by executing a dynamic query as part of Select statement , except seems i don't think we can use execute immediate in an select clause. Could i get some help with syntax or understanding as to better way to do this.
SELECT
owner OWNER,
table_name,
column_name,
'select max(length('||column_name||')) from '||table_name||';' max_data_length
FROM
dba_tab_columns
WHERE
AND ( data_type = 'NUMBER'
OR data_type = 'INTEGER' )
the 4th column in above query spits out a sql string rather than computing the value and returning it.

Here is some food for thought. Note that I am only looking for numeric columns that don't already have precision specified in the catalog. (If you prefer, you can audit all numeric columns and compare the declared precision against the actual precision used by your data.)
I am also looking only in specific schemas. Instead, you may give a list of schemas to be ignored; I hope you are not seriously considering making any changes to SYS, for example, even if it does (and it does!) have numeric columns without specified precision.
The catalog doesn't store INTEGER in the data type; instead, it stores that as NUMBER(38) So I am not searching for data type INTEGER in DBA_TAB_COLUMNS. But this raises an interesting question - perhaps you should search for all columns where DATA_PRECISION is null (as in my code below), but also for DATA_PRECISION = 38.
In the code below I use DBMS_OUTPUT to display the findings directly to the screen. You will probably want to do something smarter with this; either create a table function, or create a table and insert the findings in it, or perhaps even issue DDL already (note that those also require dynamic SQL).
This still leaves you to deal with scale. Perhaps you can get around that with a specification like NUMBER(prec, *) - not sure if that will meet your needs. But the idea is similar; you will just need to write code carefully, like I did for precision (accounting for the decimal point and the minus sign, for example).
Long story short, here is what I ran on my system, and the output it produced.
declare
prec number;
begin
for rec in (
select owner, table_name, column_name
from all_tab_columns
where owner in ('SCOTT', 'HR')
and data_type = 'NUMBER'
and data_precision is null
)
loop
execute immediate
'select max(length(translate(to_char(' || rec.column_name ||
'), ''0-.'', ''0'')))
from ' || rec.owner || '.' || rec.table_name
into prec;
dbms_output.put_line('owner: ' || lpad(rec.owner, 12, ' ') ||
' table name: ' || lpad(rec.table_name, 12, ' ') ||
' column_name: ' || lpad(rec.column_name, 12, ' ') ||
' precision: ' || prec);
end loop;
end;
/
PL/SQL procedure successfully completed.
owner: HR table name: REGIONS column_name: REGION_ID precision: 1
owner: HR table name: COUNTRIES column_name: REGION_ID precision: 1
owner: SCOTT table name: SALGRADE column_name: GRADE precision: 1
owner: SCOTT table name: SALGRADE column_name: LOSAL precision: 4
owner: SCOTT table name: SALGRADE column_name: HISAL precision: 4
PL/SQL procedure successfully completed.
EDIT
Here are several additional points (mostly, corrections) based on extended conversations with Sayan Malakshinov in comments to my answer and to his.
Most importantly, even if we can figure out max precision of numeric columns, that doesn't seem directly related to the ultimate goal of this whole thing, which is to determine the correct Postgre data types for the existing Oracle columns. For example in Postgre, unlike Oracle, it is important to distinguish between integer and non-integer. Unless scale is explicitly 0 in Oracle, we don't know that the column is "integers only"; we could find that out, through a similar dynamic SQL approach, but we would be checking for non-integer values, not precision.
Various corrections: My query is careless with regard to quoted identifiers (schema name, table name, column name). See the proper use of double-quotes in the dynamic query in Sayan's answer; my dynamic query should be modified to use double-quotes in the same way his does.
In my approach I pass numbers through TO_CHAR and then remove minus sign and decimal period. Of course, one's system may use comma, or other symbols, for decimal separator; the safer approach is to remove everything that is not a digit. That can be done with
translate(col_name, '0123456789' || col_name, '0123456789')
The query also doesn't handle very large or very small numbers, which can be stored in the Oracle database, but can only be represented in scientific notation when passed through TO_CHAR().
In any case, since "max precision" doesn't seem directly related to the ultimate goal of mapping to correct data types in Postgre, I am not changing the code - leaving it in the original form.
Thanks to Sayan for pointing out all these issues.
One more thing - *_TAB_COLUMNS contains information about view columns too; very likely those should be ignored for the task at hand. Very easy to do, as long as we realize it needs to be done.

Reading carefully that AWS article and since both previous answers (including mine) use rough estimate (length+to_char without format model and vsize operate decimal length, not bytes), I decided to write another full answer.
Look at this simple example:
with
function f_bin(x number) return varchar2 as
bi binary_integer;
e_overflow exception;
pragma exception_init(e_overflow, -1426);
begin
bi:=x;
return case when bi=x then 'ok' else 'error' end;
exception when e_overflow then return 'error';
end;
function f_check(x number, f varchar2) return varchar2 as
begin
return case when to_number(to_char(abs(x),f),f) = abs(x) then 'ok' else 'error' end;
exception when VALUE_ERROR then return 'error';
end;
a(a1) as (
select * from table(sys.odcinumberlist(
1,
0.1,
-0.1,
-7,
power(2,15)-1,
power(2,16)-1,
power(2,31)-1,
power(2,32)-1
))
)
select
a1,
f_check(a1,'fm0XXX') byte2,
f_check(a1,'fm0XXXXXXX') byte4,
f_bin(a1) ff_signed_binary_int,
to_char(abs(a1),'fm0XXXXXXXXXXXXXXX') f_byte8,
f_check(a1,'fm0XXXXXXXXXXXXXXX') byte8,
vsize(a1) vs,
dump(a1) dmp
from a;
Result:
A1 BYTE2 BYTE4 FF_SIGNED_ F_BYTE8 BYTE8 VS DMP
---------- ---------- ---------- ---------- ---------------- ---------- ---------- ----------------------------------------
1 ok ok ok 0000000000000001 ok 2 Typ=2 Len=2: 193,2
.1 error error error 0000000000000000 error 2 Typ=2 Len=2: 192,11
-.1 error error error 0000000000000000 error 3 Typ=2 Len=3: 63,91,102
-7 ok ok ok 0000000000000007 ok 3 Typ=2 Len=3: 62,94,102
32767 ok ok ok 0000000000007FFF ok 4 Typ=2 Len=4: 195,4,28,68
65535 ok ok ok 000000000000FFFF ok 4 Typ=2 Len=4: 195,7,56,36
2147483647 error ok ok 000000007FFFFFFF ok 6 Typ=2 Len=6: 197,22,48,49,37,48
4294967295 error ok error 00000000FFFFFFFF ok 6 Typ=2 Len=6: 197,43,95,97,73,96
Here I used PL/SQL functions for readability and to make it more clear.
Function f_bin casts an input number parameter to PL/SQL binary_integer (signed int4) and compares the result with input parameter, ie it checks if it loses accuracy. Defined exception shows that it can raise an exception 1426 "numeric overflow".
Function f_check does double conversion to_number(to_char(...)) of the input value and checks if it's still equal to the input value. Here I use hexadecimal format mask (XX = 1 byte), so it checks if an input number can fit an in this format mask. Hexadecimal format model works with non-negative numbers, so we need to use abs() here.
F_BYTE8 shows formatted value that uses a function from the column BYTE8, so you can easily see the loss of accuracy here.
All the above were just for readability, but we can make the same using just pure SQL:
with
a(a1) as (
select * from table(sys.odcinumberlist(
1,
0.1,
-0.1,
-7,
power(2,15)-1,
power(2,16)-1,
power(2,31)-1,
power(2,32)-1
))
)
select
a1,
case when abs(a1) = to_number(to_char(abs(a1),'fmXXXXXXXXXXXXXXX') default null on conversion error,'fmXXXXXXXXXXXXXXX')
then ceil(length(to_char(abs(a1),'fmXXXXXXXXXXXXXXX'))/2)
else -1
end xx,
vsize(a1) vs,
dump(a1) dmp
from a;
Result:
A1 XX VS DMP
---------- ---------- ---------- ----------------------------------------
1 1 2 Typ=2 Len=2: 193,2
.1 -1 2 Typ=2 Len=2: 192,11
-.1 -1 3 Typ=2 Len=3: 63,91,102
-7 1 3 Typ=2 Len=3: 62,94,102
32767 2 4 Typ=2 Len=4: 195,4,28,68
65535 2 4 Typ=2 Len=4: 195,7,56,36
2147483647 4 6 Typ=2 Len=6: 197,22,48,49,37,48
4294967295 4 6 Typ=2 Len=6: 197,43,95,97,73,96
As you can see, here I return -1 in case of conversion errors to byte8 and number of non-zero bytes otherwize.
Obviusly it can be simplified even more: you can just check range limits and that x=trunc(x) or mod(x,1)=0.

Looks like that is what you need:
VSIZE returns the number of bytes in the internal representation of expr. If expr is null, then this function returns null.
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/VSIZE.html
In Oracle INTEGER is just a number(*,0): http://orasql.org/2012/11/10/differences-between-integerint-in-sql-and-plsql/
select
owner,table_name,column_name,
data_type,data_length,data_precision,data_scale,avg_col_len
,x.vs
from (select/*+ no_merge */ c.*
from dba_tab_columns c
where data_type='NUMBER'
and owner not in (select username from dba_users where ORACLE_MAINTAINED='Y')
) c
,xmltable(
'/ROWSET/ROW/VSIZE'
passing dbms_xmlgen.getxmltype('select nvl(max(vsize("'||c.column_name||'")),0) as VSIZE from "'||c.owner||'"."'||c.table_name||'"')
columns vs int path '.'
) x
;
Update: if you read oracle internal number format (exponent+bcd-mantissa and look at a result of dump(x) function, you can see that Oracle stores numbers as a decimal value(4 bits per 1 decimal digit, 2 digits in 1 byte), so for such small ranges you can just take their maximum BCD mantissa+1 exponent as a rough estimation.

Related

Oracle plsql how to check if number has decimal points

Just started working with oracle using toad ide. trying to format the numbers from a table in specific format. the numbers come in from a variable in the table and I want to display the whole numbers as whole numbers and display floats as floats. So far, I can use trim(TO_CHAR (width,'999.999')) to display all numbers with decimal points.
For example: 123.5 will be displayed as 123.500 and 100 will be displayed as 100.000.
What I want to do is display for eg: 100 as 100.
Hope this is clear and I get a solution soon.
I'm using MOD for determining decimals.
select test_value, (case when mod(test_value,1) != 0 then 'DECIMAL' else 'NODECIMAL' END) IS_DECIMAL
from (select 1.5 test_value from dual
union all
select 100 test_value from dual) test_table
If your problem is about the way Toad shows numbers, you can follow the hints in the comments.
If the problem is about the way Oracle shows numbers, converting them to strings, maybe this can help:
SQL> select to_char(1.5, 'TM9') as num from dual union all
2 select to_char(100, 'TM9') from dual;
NUM
----------------------------------------------------------------
1,5
100
You find much more in the documentation
If you need a way to check whether a number has a decimal part or not, you can simply try:
SQL> with numbers(num) as (
2 select 1.5 from dual union all
3 select 100 from dual
4 )
5 select case
6 when floor(num) = num
7 then to_char(num, 'FM999999') || ' has not a decimal part'
8 else
9 to_char(num, 'FM9999D000') || ' has a decimal part'
10 end as checkString
11 from numbers;
CHECKSTRING
------------------------------
1,500 has a decimal part
100 has not a decimal part

Why oracle doesn't show the integer part of a decimal

when I execute the next SQL:
select 1/3 from dual;
the result is ,333333333
but the result that I expected was 0,333333333
Why doesn't oracle show the zero ? is there any parameter that can affect it?
Thanks
Edit: I checked this behaviour from an aplication that uses the oo4o (oracle object for ole), and confirmed with the sqlDeveloper (when I execute the sql with F5, not with ctrl+intro). The real problem is with the oo4o Dynaset object: when I try to sum the value of two fields, what I get is a concatenation, not a sum: ,3 + ,2 = ,3,2.
What I want to know is if there is some parameter or configuration that makes the oracle engine return the number without the zero, so I can turn on|off in order to return de zero as integer part.
SQL*Plus will show that by default (using your territory's decimal separator):
SQL> select 1/3 from dual;
1/3
----------
.333333333
You could use set numformat to change the behaviour:
SQL> set numformat "0D9999"
SQL> select 1/3 from dual;
1/3
-------
0.3333
... where the D represents the decimal separator. Or you could use column formatting, with a column alias:
SQL> set numformat ""
SQL> column answer format 0.000
SQL> select 1/3 as answer from dual;
ANSWER
------
0.333
Other clients have different ways of controlling the default output; SQL Developer behaves much the same, but PL/SQL Developer, Toad etc. might not.
Or you can format the number as part of the query, which isn't client-dpendent:
SQL> select to_char(1/3, '9990D99999') from dual;
TO_CHAR(1/3
-----------
0.33333
You need to provide enough digits for the integer part of whatever you're calculating though. Anything less than zero is simple, but if there are too many digits before the decimal separator then it won't display at all:
SQL> select to_char(100000/3, '9990D99999') from dual;
TO_CHAR(100
-----------
###########

Oracle cursor removes leading zero

I have a cursor which selects date from column with NUMBER type containg floating point numbers. Numbers like 4,3433 are returned properly while numbers smaller then 1 have removed leading zero.
For example number 0,4513 is returned as ,4513.
When I execute select used in the cursor on the database, numbers are formatted properly, with leading zeros.
This is how I loop over the recors returned by the cursor:
FOR c_data IN cursor_name(p_date) LOOP
...
END LOOP;
Any ideas why it works that way?
Thank you in advance.
You're confusing number format and number value.
The two strings 0.123 and .123, when read as a number, are mathematically equals. They represent the same number. In Oracle the true number representation is never displayed directly, we always convert a number to a character to display it, either implicitly or explicitly with a function.
You assume that a number between 0 and 1 should be represented with a leading 0, but this is not true by default, it depends on how you ask this number to be displayed. If you don't want unexpected outcome, you have to be explicit when displaying numbers/dates, for example:
to_char(your_number, '9990.99');
It's the default number formatting that Oracle provides.
If you want to specify something custom, you shall use TO_CHAR function (either in SQL query or PL/SQL code inside the loop).
Here is how it works:
SQL>
SQL> WITH aa AS (
2 select 1.3232 NUM from dual UNION ALL
3 select 1.3232 NUM from dual UNION ALL
4 select 332.323 NUM from dual UNION ALL
5 select 0.3232 NUM from dual
6 )
7 select NUM, to_char(NUM, 'FM999990D9999999') FORMATTED from aa
8 /
NUM FORMATTED
---------- ---------------
1.3232 1.3232
1.3232 1.3232
332.323 332.323
.3232 0.3232
SQL>
In this example, 'FM' - suppresses extra blanks, '0' indicates number digit including leading/trailing zeros, and '9' indicates digit suppressing leading/trailing zeros.
You can find many examples here:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements004.htm#i34570

Oracle BINARY_FLOAT: 2 integers give same value?

I have a table full of 10-digit integers and thought to speed up queries/math in Oracle by storing them as BINARY_FLOAT. That's more CPU-friendly than NUMBER and won't take as much space (I think), which means more data in memory.
However, it appears that BINARY_FLOAT yields the same bytes (and hence value) for two different numbers...which obviously won't work.
Example:
SQL> select dump(to_binary_float(25185387)) from dual;
DUMP(TO_BINARY_FLOAT(2518538
----------------------------
Typ=100 Len=4: 203,192,38,54
SQL> select dump(to_binary_float(25185388)) from dual;
DUMP(TO_BINARY_FLOAT(2518538
----------------------------
Typ=100 Len=4: 203,192,38,54
SQL> CREATE TABLE blah ( somenum BINARY_FLOAT );
Table created.
SQL> insert into blah (somenum) values (25185387);
1 row created.
SQL> insert into blah (somenum) values (25185388);
1 row created.
SQL> select somenum from blah;
SOMENUM
----------
2.519E+007
2.519E+007
SQL> select to_number(somenum) from blah;
TO_NUMBER(SOMENUM)
------------------
25185388
25185388
SQL> select dump(somenum) from blah;
DUMP(SOMENUM)
------------------------------------------------------------------------------------------------------------------------
Typ=100 Len=4: 203,192,38,54
Typ=100 Len=4: 203,192,38,54
I expected that if I got into floating point, I might have some problem, but these are integers. I've tried various incantations - 25185387f, 25185387.0, 25185387*1.0, to_number(25185387), etc.
As I read the docs, BINARY_FLOAT should store to 1.79e308, so it can't be a rounding problem.
I'm using Oracle 11.2.0.3 on a 64-bit platform.
Ideas? Thanks.
Since the implementation of the oracle is BINARY_FLOAT standard ieee 754. BINARY_FLOAT is same as singe.
single have only 23 bits for significant bits.
25185387 = 11000000001001100011010 11 (length = 25)
25185388 = 11000000001001100011011 00 (length = 25)
hence the importance of these oracle rounds, discarding the least significant bits
25185387 ~ 11000000001001100011011 * 2^2
25185388 ~ 11000000001001100011011 * 2^2
so get the same value

literal string works but variables take forever

I have a query that works when I have fixed values. ie:
select
count(*)
from
address a
where
a.primary_name like upper('cambourne court') and
a.secondary_name like upper('flat 9');
However replace the upper('flat 9') with a variable which is second_name:=upper('flat 9') and the search now returns all 111 addresses in 'cambourne court'.
Why would this be?
EDIT: This is the complete address.sql file (with comments removed)
declare
address_details address%rowtype;
current_loc varchar2(32);
prime_name varchar2(255);
prime_number varchar2(255);
second_name varchar2(255);
street_name varchar2(255);
town_name varchar2(255);
success boolean;
the_count number;
begin
prime_name:=upper('&&primary_name');
prime_number:=upper('&&primary_number');
second_name:=upper('&&secondary_name');
street_name:=upper('&&street_name');
town_name:=upper('&&town_name');
success:=true;
-- error checking here (removed for brevity)
if success then
current_loc := 'finding address';
select
count(*)
into
the_count
from
dependency d,
address a,
street s
where
d.dep_obj_id1 = 2 and
d.dep_obj_id2 = 1 and
a.loc_id = d.dep_id1 and
s.loc_id = d.dep_id2 and
a.primary_name like prime_name and
a.secondary_name like second_name and
s.name like street_name and
s.town like town_name;
end if;
dbms_output.put_line('success: address found '||the_count);
exception
when too_many_rows then
dbms_output.put_line('failure: too many rows while '||current_loc);
when no_data_found then
dbms_output.put_line('failure: no rows found while '||current_loc);
when others then
dbms_output.put_line('failure: general error while '||current_loc);
end;
/
Update: I restarted SQL*Plus which seemed to have fixed the break.
Replacing prime_name and second_name with the actual strings means the code runs in less than a second. With variables means it takes more than 2 minutes.
Your symptoms correspond to having a PL/SQL variable with the same name as a column in the table.
[Edit]
feeling somewhat guilty with an upvote that wasn't the correct answer, so I tried to reproduce and don't get your results:
SQL> select * from address
2 ;
PRIMARY_NAME SECONDARY_NAME
------------------------------ ------------------------------
CAMBOURNE COURT FLAT 9
CAMBOURNE COURT FLAT 10
SQL> declare
2 second_name varchar2(30) := upper('flat 9');
3 x pls_integer;
4 cursor c is
5 select
6 count(*)
7 from address a
8 where
9 a.primary_name like upper('cambourne court') and
10 a.secondary_name like upper('flat 9')
11 ;
12 begin
13 select count(*) into x
14 from address a
15 where
16 a.primary_name like upper('cambourne court') and
17 a.secondary_name like upper('flat 9');
18 dbms_output.put_line('literal: '||x);
19 select count(*) into x
20 from address a
21 where
22 a.primary_name like upper('cambourne court') and
23 a.secondary_name like second_name;
24 dbms_output.put_line('variable: '||x);
25 end;
26 /
literal: 1
variable: 1
PL/SQL procedure successfully completed.
The 111 records suggests second_name doesn't contain the value you expect; how are you capturing &&secondary_name, and can you check the value it actually has before and after your omitted validation section? From the results it seems to contain '%' rather than 'flat 9', but I assume you've already checked that.
The speed issue suggests the optimiser is changing behaviour in a way that's changing the join order and/or the indexes being used. By default that could be joining every street row with every every address record that has a Cambourne Court and only then doing the dependency checks, but it will vary quite a bit based on what indexes it thinks it can use and any stats that are available. The difference is that with the literals, even though you're using like there are no wildcards so it may know it can use an index on the primary_name and/or secondary_name; in the variable version it can't know that when the query is parsed so has to assume worse-case, which would be '%'. Which it may actually be getting if it's returning 111 addresses.
Without doing an explain plan it's hard to guess exactly what's going on, but you could try adding some optimiser hints to at least try and get the join order right, and even to use an index - though that should possibly not stay in place if you can ever have values starting with %. That might tell you what's being done differently.
The explain plan may be suggestive.
After running it, find the sql_id from v$sql for that statemnet
select sql_text, sql_id from v$sql where lower(sql_text) like '%address%street%';
Then plug that into
select * from table(dbms_xplan.display_cursor('1mmy8g93um377'));
What you should see at the bottom is something like this, which would show whether there were any oddities in the plan (eg using a column in one of the tables, using a function...).
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("A"."LOC_ID"="D"."DEP_ID1" AND "S"."LOC_ID"="D"."DEP_ID2")
4 - filter(("A"."PRIMARY_NAME" LIKE :B4 AND "A"."SECONDARY_NAME" LIKE
:B3))
6 - filter(("S"."NAME" LIKE :B2 AND "S"."TOWN" LIKE :B1))
7 - filter(("D"."DEP_OBJ_ID1"=2 AND "D"."DEP_OBJ_ID2"=1))
Alex has pointed the probable cause. Tables are indexed and using "like" with a variable is a case of index deactivation. Optimizers treat "like" expressions with constants that have no wildcards or placeholders as "=", so indexes if present are considered.
Drop your index on those columns and you'll get same bad performance with constants or variables. Actually don't do it, just autotrace and compare plans.
Regards,

Resources