What is the exact NULL value for a field in Oracle? - oracle

Title says it all pretty much. What is the exact value that is assigned to a(n) a)Arithmetic b)String c)Logical field to represent NULL, in Oracle DBMS?
Thank you for your time!

Null is the absence of meaning, the absence of value. What gets assigned is null. Not even an ASCII null (ascii value 0) but nothing.
That's why there's a special operation to test for null . This will return false:
...
where col1 = null
We need to test for:
where col1 is null
"we were asked by a professor at uni to find what exactly that value is in these 3 respective cases"
Okay, let's investigate that. Here is a table with two rows:
SQL> create table t42 (
2 type varchar2(10)
3 , colv varchar2(10)
4 , coln number
5 , cold date
6 )
7 /
Table created.
SQL> insert into t42 values ('not null', 'X', 1, sysdate);
1 row created.
SQL> insert into t42 values ('all null', null, null, null);
1 row created.
SQL>Exp
Oracle has a function dump() which shows us the datatype and content of the passed value. Find out more.
What does dump() tell us about our two rows?
SQL> select type
2 , dump(colv) as colv
3 , dump(coln) as coln
4 , dump(cold) as cold
5 from t42;
TYPE COLV COLN COLD
---------- -------------------- -------------------- ----------------------------------
not null Typ=1 Len=1: 88 Typ=2 Len=2: 193,2 Typ=12 Len=7: 120,117,4,29,6,60,44
all null NULL NULL NULL
SQL>
So: the null columns have no data type, no value.
"I don't think dump is suitable for supporting any argument over what "exactly" gets stored to represent a null - because if the expression is null, it simply returns null by definition "
#JeffreyKemp makes a fair point. So let's dip a toe into the internals. The first step is to dump the data block(s);l the dump is written to a trace file:
SQL> conn / as sysdba
Connected.
USER is "SYS"
SQL> select dbms_rowid.rowid_relative_fno(t42.rowid) as fno
2 , dbms_rowid.rowid_block_number(t42.rowid) as blk
3 from a.t42
4 /
FNO BLK
-------- --------
11 132
11 132
SQL> alter system dump datafile 11 block 132;
System altered.
SQL> select value from v$diag_info where name = 'Default Trace File';
VALUE
--------------------------------------------------------------------------------
/home/oracle/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_3275.trc
SQL>
Because T42 is small it fits into only one block. Here is the interesting bit of the dump:
data_block_dump,data header at 0x805664
===============
tsiz: 0x1f98
hsiz: 0x16
pbl: 0x00805664
76543210
flag=--------
ntab=1
nrow=2
frre=-1
fsbo=0x16
fseo=0x1f73
avsp=0x1f5d
tosp=0x1f5d
0xe:pti[0] nrow=2 offs=0
0x12:pri[0] offs=0x1f7f
0x14:pri[1] offs=0x1f73
block_row_dump:
tab 0, row 0, #0x1f7f
tl: 25 fb: --H-FL-- lb: 0x1 cc: 4
col 0: [ 8] 6e 6f 74 20 6e 75 6c 6c
col 1: [ 1] 58
col 2: [ 2] c1 02
col 3: [ 7] 78 75 05 01 02 08 08
tab 0, row 1, #0x1f73
tl: 12 fb: --H-FL-- lb: 0x1 cc: 1
col 0: [ 8] 61 6c 6c 20 6e 75 6c 6c
end_of_block_dump
End dump data blocks tsn: 33 file#: 11 minblk 132 maxblk 132
We can see there are two rows in the table. The first row has entries for four columns; this is the 'not null' row. The second row has only one column: this is the 'all null' row. So, Jeffrey is quite right. All the trailing fields are null so Oracle stores nothing for them.

Answer from APC is fully right, let's give some information on "what does it mean":
Arithmetic: NULL basically means "not defined". Every math operation with NULL (i.e. "not defined") also returns NULL
String: NULL is an empty string, i.e. '' IS NULL returns TRUE - this behavior of Oracle is different to many others RDBMS.
Logical: I assume you mean what happens to BOOLEAN data types. Unlike almost any other programming language in PL/SQL a BOOLEAN variable can have three different states: TRUE, FALSE and NULL. Be aware of this special behavior when you work with BOOLEAN in PL/SQL.

In addition to #APC, in DB there is a something like 'ternary logic' in comparison operations, when we can say that value are equal, not equal and the third is "we don't know", cause of it value absence NULL, and even comparison of to NULL values gives the NULL, meaning that we have no information about operands

Related

Execute immediate use in select * from in 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.

How to find Max value of an alphanumeric field in oracle?

I have the data as below and ID is VARCHAR2 type
Table Name :EMP
ID TST_DATE
A035 05/12/2015
BAB0 05/12/2015
701 07/12/2015
81 07/12/2015
I used below query to get max of ID group by TST_DATE.
SELECT TST_DATE,MAX(ID) from EMP group by TST_DATE;
TST_DATE MAX(ID)
05/12/2015 BAB0
07/12/2015 81
In the second row it returning 81 instead of 701.
To sort strings that represent (hex) numbers in numeric, rather than lexicographical, order you need to convert them to actual numbers:
SELECT TST_DATE, ID, TO_NUMBER(ID, 'XXXXXXXXXX') from EMP
ORDER BY TO_NUMBER(ID, 'XXXXXXXXXX');
TST_DATE ID TO_NUMBER(ID,'XXXXXXXXXX')
---------- ---- ---------------------------------------
07/12/2015 81 129
07/12/2015 701 1793
05/12/2015 A035 41013
05/12/2015 BAB0 47792
You can use that numeric form within your max() and convert back to a hex string for display:
SELECT TST_DATE,
TO_CHAR(MAX(TO_NUMBER(ID, 'XXXXXXXXXX')), 'XXXXXXXXXX')
from EMP group by TST_DATE;
TST_DATE TO_CHAR(MAX
---------- -----------
07/12/2015 701
05/12/2015 BAB0
With a suitable number of Xs in the format models of course; how many depends on the size of your varchar2 column.

oracle raw type iteration

I am working with a table that contains a raw(200) field. From my client application I manage to get the value and store it in a byte[] and so that I can loop over it and get all the samples.
My raw data would be like ...
2C2B2E2B2D2C2933283030332B2F2D302F2B272F312E2B2F2F28242A2F322E
... and from there I would like to go from hex to decimal values and get an array such as 44,43,46,43
However, I would like to do a similar thing in a procedure but I don't know how to iterate over a raw field or how to cast it to byte array.
I tried with UTL_RAW.CAST_TO_BINARY_INTEGER but that would only give me the first sample
Given this data ...
SQL> select col1
2 from t23
3 /
COL1
--------------------------------------------------------------------------------
32433242324532423244324332393333323833303330333332423246324433303246324232373246
33313245324232463246323832343241324633323245
SQL>
... a SELECT like this will produce the requisite output...
SQL> select regexp_substr(utl_raw.cast_to_varchar2(col1), '([A-Z0-9]{2})', 1, level)
2 from t23
3 connect by level <= ceil(utl_raw.length(col1)/2)
4 /
REGEXP_SUBSTR(UTL_RAW.CAST_TO_VARCHAR2(COL1),'([A-Z0-9]{2})',1,LEVEL)
--------------------------------------------------------------------------------
2C
2B
2E
2B
...
2B
2F
2F
28
24
2A
2F
32
2E
31 rows selected.
SQL>
Use TO_NUMBER with the 'XX' mask to convert the hex into decimal ...
SQL> select to_number(
2 regexp_substr(utl_raw.cast_to_varchar2(col1), '([A-Z0-9]{2})', 1, level)
3 , 'XX')
4 from t23
5 connect by level <= ceil(utl_raw.length(col1)/2)
6 /
TO_NUMBER(REGEXP_SUBSTR(UTL_RAW.CAST_TO_VARCHAR2(COL1),'([A-Z0-9]{2})',1,LEVEL),
--------------------------------------------------------------------------------
44
43
46
43
45
44
41
...
Finally, to populate an array, and populate it in PL/SQL with the bulk collection syntax:
create type int_nt as table of integer
/
declare
ints int_nt;
begin
select to_number(
regexp_substr(utl_raw.cast_to_varchar2(col1), '([A-Z0-9]{2})', 1, level)
, 'XX')
bulk collect into ints
from t23
connect by level <= ceil(utl_raw.length(col1)/2);
end;
/
Probably there's a more efficient way to solve this but I managed to get my result by using utl_raw.length and utl_raw.substr over my raw data and iterating with an standard plsql loop and converting each substring to decimal with utl_raw.cast_to_binary_integer

Displaying the contents according to their heading type

I am new to SQL. My doubt is how to display the output inside the procedure like this.
I have various databases and its type and the current size all in one table 'predict_storage'
I want to display the output as:
UAT:
TMAP: 100G
TCIG: 200G
QA:
QMAP: 100G
QCIG: 200G
DR:
DRMAP: 100G
DRCIG: 200G
Where UAT is the database type and TMAP, TCIG are databases and the current size is 100g. So I want the output to like this categorized based upon the database type.
Here is the code:
CREATE OR REPLACE PROCEDURE test11 IS
db_original_name_var varchar2(30);
db_type varchar2(20);
db_name varchar2(40);
Used_space_var NUMBER;
MAX_Used_space_monthvar NUMBER;
RATE NUMBER;
avg_space_future_min NUMBER;
avg_space_future_max NUMBER;
avgsp NUMBER;
avg_space_3m NUMBER;
avg_space_6m Number;
avg_space_yr NUMBER;
CURSOR db_list_cur is
select original_db_name,database_type
from database_list;
Begin
open db_list_cur;
LOOP
fetch db_list_cur
into db_original_name_var,db_type;
EXIT WHEN db_list_cur%NOTFOUND;
select substr(avg(ave_used_space),0,6) INTO Used_space_var from month_space where db_original_name_var=database_name;
select substr(max(ave_used_space),0,6) INTO MAX_Used_space_monthvar from month_space where db_original_name_var=database_name;
--rate calc
RATE := (MAX_Used_space_monthvar-Used_space_var)/Used_space_var;
avg_space_future_max:=(Used_space_var)+(Used_space_var* RATE);
avg_space_future_min:=(Used_space_var)-(Used_space_var* RATE);
avgsp := (avg_space_future_max + avg_space_future_min)/2;
avg_space_3m :=(avgsp)+(avgsp* rate* 3);
avg_space_6m :=(avgsp)+(avgsp* rate* 6);
avg_space_yr :=(avgsp)+(avgsp* rate* 12);
insert into Predict_report
(
database_name ,
Grwoth_rate ,
Current_AVERAGE,
Space_3mn,
SPACE_6mn,
Space_yr,
database_type
)
values
(
db_original_name_var,
RATE,
round(avgsp,3),
round(avg_space_3m,3),
round(avg_space_6m,3),
round(avg_space_yr,3),
db_type
);
commit;
loop
dbms_output.put_line(db_type||':');
select database_name into db_name from predict_report where database_type=db_type;
dbms_output.put_line(db_name);
END LOOP;
END LOOP;
close db_list_cur;
END;
/
The tables I have are
1) Storage Info
DB_ID NUMBER(38)
DATABASE_NAME VARCHAR2(50)
DATABASE_SIZE NUMBER
TIME_STAMP DATE
FREE_SPACE VARCHAR2(50)
DATA_LINK_NAME VARCHAR2(50)
CUSTOMER_SPACE NUMBER
DATABASE_TYPE VARCHAR2(30)
USED_SPACE NUMBER
2) Database_list
DB_ID NUMBER
DB_NAME VARCHAR2(50)
DATALINK_NAME VARCHAR2(50)
DATABASE_TYPE VARCHAR2(20)
ORIGINAL_DB_NAME VARCHAR2(30)
3)Predict Report
DATABASE_NAME NOT NULL VARCHAR2(50)
GRWOTH_RATE NUMBER
CURRENT_AVERAGE NUMBER
SPACE_3MN NUMBER
SPACE_6MN NUMBER
SPACE_YR NUMBER
DATABASE_TYPE VARCHAR2(20)
4) Month_space
DATABASE_NAME NOT NULL VARCHAR2(50)
GRWOTH_RATE NUMBER
CURRENT_AVERAGE NUMBER
SPACE_3MN NUMBER
SPACE_6MN NUMBER
SPACE_YR NUMBER
DATABASE_TYPE VARCHAR2(20)
-----The sql file is
set echo off numf 999G999G999G999 lin 32000 trims on pages 50000 head on feed off markup html off
alter session set nls_numeric_characters='.''' nls_date_format='Day DD. Month, YYYY';
spool /tmp/report.html
--prompt To: jialin.zhu#synchronoss.com
prompt TO: varun.jain#synchronoss.com
prompt cc: varun.jain#synchronoss.com
prompt From: varun.jain#synchronoss.com
prompt Subject: Daily space report
prompt Content-type: text/html
prompt MIME-Version: 1.0
set markup html on entmap off table 'BORDER="2" BGCOLOR="white" FONTCOLOR="black"'
prompt <i>Good morning, </i>
prompt <i>Here is the Space report as on &_DATE</i>
prompt <i>Kind Regards, </i>
------------------------------------------------------------------------------------------------------------------------------------------------
prompt <br/><h3>Database Space Report</h3>
set serveroutput on
CLEAR COLUMNS
Set HEADING ON
Set COLSEP ,
SET PAGESIZE 20000
SET timing off feedback off verify off echo off
prompt <br/><h3> Environment Space Summary</h3>
column database_type heading 'Database Type'
column Sum(current_average) format 9999 HEADING 'Total Space in GB'
select database_type,Sum(current_average) from predict_report group by database_type;
--------------------------------------------------------------------------------------------------------------------------------------------------
prompt <br/><h3> DR database Summary</h3>
COLUMN ('THETOTALSPACEINDRDATABASES:'||SUM(CURRENT_AVERAGE)||''||'GB') format 9999 heading 'total Space in DR database'
select ('The total space in DR databases :' ||Sum(current_average)||' '||'GB') from predict_report where DATABASE_TYPE not in ('UAT','QA');
------------------------------------------------------------------------------------------------------------------------------------------
prompt <br/><h3> databases Summary</h3>
column database_name format a30 heading ' DATABASE NAME'
column round(GRWOTH_RATE*100,0)||'%' heading 'GROWTH RATE'
COLUMN CURRENT_AVERAGE FORMAT 9999 HEADING 'TODAYS SPACE in GB'
COLUMN SPACE_3MN FORMAT 9999 HEADING 'SPACE AFT 3 MONTHS in GB'
COLUMN SPACE_6MN FORMAT 9999 HEADING 'SPACE AFT 6 MONTHS in GB'
COLUMN SPACE_YR FORMAT 9999 HEADING 'SPACE AFT A YEAR in GB'
truncate table Predict_report;
set serveroutput on
exec report;
SELECT database_name,round(GRWOTH_RATE*100,0)||'%',current_average,space_3mn,space_6mn,space_yr FROM Predict_report;
---------------------------------------------------------------------------------
prompt <br/><h3>Database Space Summary</h3>
column sum(current_average) FORMAT 9999 heading 'Total Space in GB'
column sum(space_3mn) FORMAT 9999 heading 'Total Space in 3 months in GB'
column sum(space_6mn) FORMAT 9999 heading 'Total Space in 6 months in GB'
column sum(space_yr) FORMAT 9999 heading 'Total Space in 1 year in GB'
select sum(current_average),sum(space_3mn),sum(space_6mn),sum(space_yr) from Predict_report ;
spool off;
---------to send the mail----------------
host /usr/sbin/sendmail -t </tmp/report.html
and the output in the email received is like (It is in table form in the EMAIL)
DATABASE NAME GROWTH RATE TODAYS SPACE in GB SPACE AFT 3 MONTHS in GB SPACE AFT 6 MONTHS in GB SPACE AFT A YEAR in GB
CLNK 0% 199 200 200 202
CCIG 0% 562 563 563 565
DTXN 5% 330 377 424 518
DCIG 0% 414 416 418 422
QMAP 0% 16 16 17 17
QLNP 0% 44 44 44 44
QHTS 1% 32 32 33 34
QFKP 1% 37 38 39 41
QSAG 0% 168 169 170 172
CSAG 0% 812 815 818 824
LTATG 0% 25 25 25 25
QCIG 0% 208 209 209 211
TLNP 0% 341 341 341 341
TMAP 0% 60 61 61 62
TSAG 0% 223 226 228
The extra loop you have now for output never terminates, so executing the procedure will appear to hang, at least until you run out of dbms_output buffer space; and doesn't retrieve the current value. And it's in the wrong place, as it will show data for all databases of that type each time round the loop, so you'll get duplicates (at best).
Although it doesn't seem to be a sensible way to do it, not least because you're assuming the procedure will always be run by something that can show the output and has the output enabled, you could do something like this, after close db_list_cur:
db_type := null;
for r in (select database_type, database_name, current_average
from predict_report
order by database_type, database_name)
loop
if db_type is null or db_type != r.database_type then
dbms_output.put_line(r.database_type || ':');
db_type := r.database_type;
end if;
dbms_output.put_line(r.database_name ||' '||
to_char(nvl(r.current_average, 0), '999990.00') ||'G');
end loop;
... or within the main loop based on the data you're inserting, rather than querying the table at the end (since they should match); but this will work whether you use your loops or a much simpler insert into predict_report select ....
You don't need to use cursors to calculate the statistics or populate the table; you don't need PL/SQL at all really. You can just join the two tables together and work out the numbers from the returned values. Something like this would do it:
select database_name,
rate as grwoth_rate,
avg_used_space as current_average,
avg_used_space + (avg_used_space * rate * 3) as space3mn,
avg_used_space + (avg_used_space * rate * 6) as space6mn,
avg_used_space + (avg_used_space * rate * 12) as spaceyr,
database_type
from (
select database_name,
database_type,
avg_used_space,
max_used_space,
(max_used_space - avg_used_space)/avg_used_space as rate
from (
select dl.original_db_name as database_name,
dl.database_type,
trunc(avg(ave_used_space), 2) as avg_used_space,
trunc(max(ave_used_space), 2) as max_used_space
from database_list dl
join month_space ms on ms.database_name = dl.original_db_name
group by dl.original_db_name,
dl.database_type
)
);
You can use that to insert into the new table, though not sure you even need that really, if you're only producing output from within the procedure - not clear if that's the case, though you don't seem to want to show all the statistics you're calculating.
I'm also not sure your statistics make much sense though. You seem to be calculating a growth rate over an arbitrary number of months, and then predicting future sizes by applying that rate to the average over the life of the database rather than it's current size, which you don't seem to be collecting - you have the current average, which isn't the same thing.

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

Resources