`
xiang37
  • 浏览: 414279 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Oracle 数据类型及存储方式

阅读更多

it's from http://www.iteye.com/topic/220760

 

 

Oracle 数据类型及存储方式 
袁光东 原创 
概述 
通过实例,全面而深入的分析oralce的基本数据类型及它们的存储方式。以ORACLE 10G为基础,介绍oralce 10g引入的新的数据类型。让你对oracle数据类型有一个全新的认识。揭示一些不为人知的秘密和被忽略的盲点。从实用和优化的角度出发,讨论每种数据类型的特点。从这里开始oracle之旅! 
第一部份 字符类型 
§1.1  char 
定长字符串,会用空格来填充来达到其最大长度,最长2000个字节。 

1.	新建一个测试表test_char.,只有一个char类型的列。长度为10 
SQL> create table test_char(colA char(10)); 
Table created 
2.	向这个表中插入一些数据。 
SQL> insert into test_char values('a'); 

1 row inserted 

SQL> insert into test_char values('aa'); 

1 row inserted 

SQL> insert into test_char values('aaa'); 

1 row inserted 

SQL> insert into test_char values('aaaa'); 

1 row inserted 

SQL> insert into test_char values('aaaaaaaaaa'); 
1 row inserted 

注意:最多只能插入10个字节。否是就报错。 
SQL> insert into test_char values('aaaaaaaaaaa'); 

insert into test_char values('aaaaaaaaaaa') 

ORA-12899: value too large for column "PUB_TEST"."TEST_CHAR"."COLA" (actual: 11, maximum: 10) 

3.	使用dump函数可以查看每一行的内部存数结构。 
SQL> select colA, dump(colA) from test_char; 

COLA       DUMP(COLA) 
---------- -------------------------------------------------------------------------------- 
a          Typ=96 Len=10: 97,32,32,32,32,32,32,32,32,32 
aa         Typ=96 Len=10: 97,97,32,32,32,32,32,32,32,32 
aaa        Typ=96 Len=10: 97,97,97,32,32,32,32,32,32,32 
aaaa       Typ=96 Len=10: 97,97,97,97,32,32,32,32,32,32 
aaaaaaaaaa Typ=96 Len=10: 97,97,97,97,97,97,97,97,97,97 

注意:Typ=96 表示数据类型的ID。Oracle为每一种数据类型都进行了编号。说明char类型的编号是96. 
Len =10 表示所在的内部存储的长度(用字节表示)。虽然第一例只存了一个字符’a’,但是它还是占用了10个字节的空间。 
97,32,32,32,32,32,32,32,32,32 表示内部存储方式。可见oracle的内部存储是以数据库字符集进行存储的。 
97正好是字符a的ASCII码。 
可以使用chr函数把ASCII码转成字符。 
SQL> select chr(97) from dual; 

CHR(97) 
------- 
a 

要想知道一个字符的ASCII码,可以使用函数ascii 
SQL> select ascii('a') from dual; 

ASCII('A') 
---------- 
        97 

32正好是空格的ascii码值。 
Char类型是定长类型。它总会以空格来填充以达到一个固定宽度。 
使用char类型会浪费存储空间。 
Oracle的数据类型的长度单位是字节。 
SQL> select dump('汉') from dual; 

DUMP('汉') 
--------------------- 
Typ=96 Len=2: 186,186 

可见一个汉字在oracle中是占用了两个字节的。 
英文字母或符号只占用一个字节。 
Char(10)最多可存放5个汉字。 
§1.2  varchar2 
是一种变长的字符类型。最多可占用4000字节的存储空间。 
1.	创建一个表,只有一列,类型为varchar2,长度为10 
SQL> create table test_varchar( col varchar2(10)); 

Table created 
2. 插入一些数据 
SQL> insert into test_varchar values('a'); 

1 row inserted 

SQL> insert into test_varchar values('aa'); 

1 row inserted 

SQL> insert into test_varchar values('aaa'); 

1 row inserted 

SQL> insert into test_varchar values('aaaaaaaaaa'); 

1 row inserted 

SQL> insert into test_varchar values('aaaaaaaaaaa'); 

2.	用dump函数查看每一行的内部存储结构。 
SQL> select col, dump(col) from test_varchar; 

COL        DUMP(COL) 
---------- -------------------------------------------------------------------------------- 
a          Typ=1 Len=1: 97 
aa         Typ=1 Len=2: 97,97 
aaa        Typ=1 Len=3: 97,97,97 
aaaaaaaaaa Typ=1 Len=10: 97,97,97,97,97,97,97,97,97,97 


Typ=1,说明varchar2类型在oracle中的类型编号为1 
Len代表了每一行数据所占用的字节数。 
后面是具体的存储值。 
由此可见,varchar2是存多少就占用多少空间。比较节省空间的。不会像char那样用空格填充。 

§1.3  byte 和char 
在10g中,字符类型的宽度定义时,可以指定单位。 
Byte就是字节。 
Char就是字符。 
Varchar2(10 byte) 长度为10个字节。 
Varchar2(10 char) 长度为10个字符所占的长度。 
Char(10 byte)长度为10个字节。 
Char(10 char) 长度为10个字符所占的长度。 
一个字符占用多少个字节,是由当前系统采用的字符集来决定的。 
如一个汉字占用两个字节。 

查看当前系统采用的字符集 
SQL> select * from nls_database_parameters where parameter ='NLS_CHARACTERSET'; 

PARAMETER                      VALUE 
------------------------------ -------------------------------------------------------------------------------- 
NLS_CHARACTERSET               ZHS16GBK 

如果在定义类型时,不指定单位。默认是按byte,即以字节为单位的。 
采用char为单位的好处是,使用多字节的字符集。 
比如,在ZHS16GBK字符集中,一个汉字占用两个字节。 
把数据表的某一列长度定义为可存放10个汉字,通过下面的定义就可以了。 
Create table test_varchar(col_char  varchar2(10 char)); 
这样相对简单一些。在数据库表设计时需要注意。 

继续实验,新建一个表,包含两列。一列采用byte为单位,一列采用char为单位 
SQL> create table test_varchar2 (col_char varchar2(10 char),col_byte varchar2(10 byte)); 

Table created 

Col_char列,定义为可存放10个字符。 
Col_byte 列,定义为可存放10个字节的字符。 
当前的系统采用字符集为ZHS16GBK.所以一个字符占两个字节。 
试着在表中插入一些数据 
SQL> insert into test_varchar2 values('a','a'); 

1 row inserted 

SQL> insert into test_varchar2 values('袁','a'); 

1 row inserted 

SQL> insert into test_varchar2 values('袁袁袁袁袁袁袁袁袁袁','aaaaaaaaaa'); 

1 row inserted 

SQL> insert into test_varchar2 values('袁袁袁袁袁袁袁袁袁袁','袁袁袁袁袁袁袁袁袁袁'); 

insert into test_varchar2 values('袁袁袁袁袁袁袁袁袁袁','袁袁袁袁袁袁袁袁袁袁') 

ORA-12899: value too large for column "PUB_TEST"."TEST_VARCHAR2"."COL_BYTE" (actual: 20, maximum: 10) 

第一次,	在两列中都插入字符a 
第二次,	在col_char列插入字符’袁’,在col_byte插入字符a 
第三次,	在col_char列中插入10个中文字符’袁’,在col_byte插入10个字符a 
第四次,	在两列中都插入中文字符’袁’时,报错了。第二列长度不够。 
   再看看每一行的存储结构 
SQL> select col_char, dump(col_char) from test_varchar2; 

COL_CHAR             DUMP(COL_CHAR) 
-------------------- -------------------------------------------------------------------------------- 
a                    Typ=1 Len=1: 97 
袁                   Typ=1 Len=2: 212,172 
袁袁袁袁袁袁袁袁袁袁 Typ=1 Len=20: 212,172,212,172,212,172,212,172,212,172,212,172,212,172,212,172,21 

当我们在col_char列插入10个汉字时,它的长度为20. 
尽管我们在定义的时候是采用varchar2(10,char). 
由此可见,oracle是根据当前数据库采用的字符集,每个字符的所占字节数 X 字段长度来决定了该字段所占的字节数。 
在本例中,varchar2(10,char)相当于varchar2(20). 
不信,我们可以试试看。 
SQL> desc test_varchar2; 
Name     Type         Nullable Default Comments 
-------- ------------ -------- ------- -------- 
COL_CHAR VARCHAR2(20) Y                         
COL_BYTE VARCHAR2(10) Y  

当采用多字节的字符集时,定义字段长度还是采用char为单位指定为佳。因为可以避免字段长度的问题。 

当不知道当前数据库采用的字符集,一个字符占用多少字节时,可以使用lengthb函数。 
SQL> select lengthb('袁') from dual; 
LENGTHB('袁') 
------------- 
            2 

§1.4  char还是varchar 
1.	新建一个表,一列为char类型,一列为varchar2类型 
SQL> create table test_char_varchar(char_col char(20),varchar_col varchar2(20)); 

Table created 
2.	向该表中的两列都插入相关的数据 
SQL> insert into test_char_varchar values('Hello World','Hello World'); 

1 row inserted 

SQL> select * from test_char_varchar; 

CHAR_COL             VARCHAR_COL 
-------------------- -------------------- 
Hello World          Hello World 

3.	以char_col列为条件查询 
SQL> select * from test_char_varchar where char_col ='Hello World'; 

CHAR_COL             VARCHAR_COL 
-------------------- -------------------- 
Hello World          Hello World 

4.	以varchar_col列为条件查询 
SQL> select * from test_char_varchar where varchar_col ='Hello World'; 

CHAR_COL             VARCHAR_COL 
-------------------- -------------------- 
Hello World          Hello World 

5.似乎char 和varchar类型没有什么两样。再看看下面的语句。 
SQL> select * from test_char_varchar where varchar_col =char_col; 

CHAR_COL             VARCHAR_COL 
-------------------- -------------------- 
这已经看出他们并不一样,这涉及到字符串比较的问题。 
因为已经发生了隐式转换,在与char列char_col进行比较时,char_col列的内容已经转换成了char(20).在Hello World后面以空格进行填充了。而varchar_col列并没有发生这种转换。 
如果要让char_col列与varchar_col列相等。有两种方法。 
第一种是:使用trim把char_col列的空格去掉。 
第二种是:使遥rpad把varchar_col列用空格进行填充长度为20的字符。 
SQL> select * from test_char_varchar where trim(char_col) = varchar_col; 

CHAR_COL             VARCHAR_COL 
-------------------- -------------------- 
Hello World          Hello World 

SQL> select * from test_char_varchar where char_col = rpad(varchar_col,20); 

CHAR_COL             VARCHAR_COL 
-------------------- -------------------- 
Hello World          Hello World 

如果使用trim函数,如果char_col列上有索引,那么索引将不可用了。 
此外还会在绑定变量时出现问题。 

 

 

 

§1.5  NCHAR和NVARCHAR2 
如果系统需要集中管理和存储多种字符集,就需要使用这两种字符类型。在使用NCAHR和NVARCHAR2时,文本内容采用国家字符集来存储和管理。而不是默认字符集。 
这两种类型的长度指的是字符数,而不是字节数。 
NLS国家语言支持(National Language Support) 
在oracle 9i及以后的版本,数据库的国家字符集可以是:utf-8和AL16UTF-16两种。 
Oracle 9i是utf -8, Oralce 10g是AL16UTF-16. 

1.新建一个表,有两列,类型分别为:nchar和nvarchar2.长度都为10 
SQL> create table test_nvarchar(col_nchar nchar(10),col_nvarchar2 nvarchar2(10)); 
Table created 

2.插入一些数据 
SQL> insert into test_nvarchar values('袁','袁光东'); 
1 row inserted 
SQL> insert into test_nvarchar values(N'袁',N'袁光东'); 
1 row inserted 
(在9i之前的版本,插入时加上N时,在处理时跟普通方式有不同的方式。但是在10g的时候已经有了改变,加不加N都是一样,这里只是为了测试) 
SQL> insert into test_nvarchar values('a','b'); 

1 row inserted 
插入一行英文字母 

3.	查看每行的col_nchar列的存储方式。 
SQL> select col_nchar, dump(col_nchar) from test_nvarchar; 

COL_NCHAR            DUMP(COL_NCHAR) 
-------------------- -------------------------------------------------------------------------------- 
袁                   Typ=96 Len=20: 136,129,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32 
a                    Typ=96 Len=20: 0,97,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32 
袁                   Typ=96 Len=20: 136,129,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32 

Typ=96 与char的类型编码一样。 
Len=20 每一行的长度都是20字节。这一点跟char一样。都是定长的,会以空格填充。 
需要注意的是:统统以两位来表示一个字符。 
136,129 表示’袁’ 
0,97 表示’a’ 
0,32 表示空格。 

4.	nvarchar2的储存 
SQL> select col_nvarchar2, dump(col_nvarchar2) from test_nvarchar; 

COL_NVARCHAR2        DUMP(COL_NVARCHAR2) 
-------------------- -------------------------------------------------------------------------------- 
袁光东               Typ=1 Len=6: 136,129,81,73,78,28 
b                    Typ=1 Len=2: 0,98 
袁光东               Typ=1 Len=6: 136,129,81,73,78,28 

Typ=1 与varchar2一样。 
每一行的len值都不样同。不会使用空格进行填充。 
每一个字符都占有两个字节两进行存储。 
b 存储为: 0, 98 
袁 存储为: 136,129 

5.nchar和nvarchar2的数据定义。 
SQL> desc test_nvarchar; 
Name          Type          Nullable Default Comments 
------------- ------------- -------- ------- -------- 
COL_NCHAR     NCHAR(20)     Y                         
COL_NVARCHAR2 NVARCHAR2(20) Y    

虽然在定义nchar和nvarchar2时,指定的长度是指字符数。但是表结构的定义中,仍然是存储着它的字节数。 
在定义时nchar(10)表示可以最大存储10个字符。 
在查看数据表结构时,显示该列最大占用的字节数。 


需要注意的是:在char和nchar中对汉字的实际存储值是不一样的。因为采用了不同的字符集,就有了不同的字符编码。 

SQL> insert into test_varchar values('袁'); 

1 row inserted 
SQL> select col, dump(col) from test_varchar where col='袁'; 

COL        DUMP(COL) 
---------- -------------------------------------------------------------------------------- 
袁         Typ=1 Len=2: 212,172 
这时采用的字符集系统默认字符集ZHS16GBK。 
这里很容易的把它转换成ascii码。 
高位 * 256(2的8次方) + 低位. 
212 * 256 + 172 = 54444 

SQL> select chr(54444) from dual; 

CHR(54444) 
---------- 
袁 

而在Nchar 和Nvarchar中,采用的是UTF-8或UTF-16的字符集。 

SQL> insert into test_nvarchar values('袁','袁'); 

1 row inserted 

SQL> select col_nvarchar2, dump(col_nvarchar2) from test_nvarchar where col_nvarchar2='袁'; 

COL_NVARCHAR2        DUMP(COL_NVARCHAR2) 
-------------------- -------------------------------------------------------------------------------- 
袁                   Typ=1 Len=2: 136,129 

‘袁’存储的值为:136,129 
Oracle 10以上对nchar和nvarchar都采用utf-16字符集了。它的好处就是对字符采用固定长度的字节存储(2字节),支持多国字符,在操作效率上会更高。但是它却无法兼容于ascii码。 
§1.6  RAW 
RAW与CHAR和VARCHAR2相比。RAW属于二进制数据,更可以把它称为二进制串。在对CHAR和VARCHAR2类型进行存储时,会进行字符集转换。而对二进制数据进行存储则不会进行字符集转换。 
SQL> create table test_raw (col_chr varchar2(10), col_raw raw(10)); 

Table created 
SQL> insert into test_raw values('aa','aa'); 

1 row inserted 

SQL> commit; 

Commit complete 

SQL> select * from test_raw; 

COL_CHR    COL_RAW 
---------- -------------------- 
aa         AA 

SQL> select col_chr,dump(col_chr) from test_raw; 

COL_CHR    DUMP(COL_CHR) 
---------- -------------------------------------------------------------------------------- 
aa         Typ=1 Len=2: 97,97 
SQL> select col_raw,dump(col_raw) from test_raw; 

COL_RAW              DUMP(COL_RAW) 
-------------------- -------------------------------------------------------------------------------- 
AA                   Typ=23 Len=1: 170 

通过上面的分析,虽然我们通过select查询得到的结果,raw列显示为插入的字符。但是我们通过dump函数得知到raw并不是以字符的方式存储。它是把插入的字符认为是16进制的值。 
比如本例,我们向raw列插入aa,但是它占用的空间为1个字节。值为170. 
170转为16进制正好是aa 
向raw列插入数据时会发生一个隐式转换HEXTORAW 
从raw列读取数据时会发生一个隐式转换RAWTOHEX 

如果向raw列插入值不是有效的十六进制值时,会报错的。 
SQL> insert into test_raw values('h','h'); 

insert into test_raw values('h','h') 

ORA-01465: invalid hex number 

 

 

 

第二部分 数值类型 
§ 2.1  number 
Number类型是oralce的数值类型,存储的数值的精度可以达到38位。Number是一种变长类型,长度为0-22字节。取值范围为:10e-130 – 10e 126(不包括) 
Number(p,s) 
P和s都是可选的。 
P指精度(precision),即总位数。默认情况下精度为38。精度的取值范围为1~38. 
S指小数位(scale).小数点右边的位数。小数点位数的合法值为-48~127。小数位的默认值由精度来决定。如果没有指定精度,小数位默认为最大的取值区间.如果指定了精度,没有指定小数位。小数位默认为0(即没有小数位). 
精度和小数位不会影响数据如何存储,只会影响允许哪些数值及数值如何舍入。 


1.新建一个表 
SQL> create table test_number(col_number number(6,2)); 

Table created 
2.插入一些不同的数据 
SQL> insert into test_number values(-1); 

1 row inserted 

SQL> insert into test_number values(0); 

1 row inserted 

SQL> insert into test_number values(1); 

1 row inserted 

SQL> insert into test_number values(2); 

1 row inserted 

SQL> insert into test_number values(11.00); 

1 row inserted 

SQL> insert into test_number values(11.11); 

1 row inserted 

SQL> insert into test_number values(1234.12); 

1 row inserted 

SQL> insert into test_number values(-0.1); 

1 row inserted 

SQL> insert into test_number values(-11.11); 

1 row inserted 

SQL> insert into test_number values(-1234.12); 

1 row inserted 
SQL> commit; 

Commit complete 

3.查看结果 
SQL> select * from test_number; 

COL_NUMBER 
---------- 
     -1.00 
      0.00 
      1.00 
      2.00 
     11.00 
     11.11 
   1234.12 
     -0.10 
    -11.11 
  -1234.12 

10 rows selected 

5.	查看存储结构 
SQL> select col_number, dump(col_number) from test_number; 

COL_NUMBER DUMP(COL_NUMBER) 
---------- -------------------------------------------------------------------------------- 
     -1.00 Typ=2 Len=3: 62,100,102 
      0.00 Typ=2 Len=1: 128 
      1.00 Typ=2 Len=2: 193,2 
      2.00 Typ=2 Len=2: 193,3 
     11.00 Typ=2 Len=2: 193,12 
     11.11 Typ=2 Len=3: 193,12,12 
   1234.12 Typ=2 Len=4: 194,13,35,13 
     -0.10 Typ=2 Len=3: 63,91,102 
    -11.11 Typ=2 Len=4: 62,90,90,102 
  -1234.12 Typ=2 Len=5: 61,89,67,89,102 

10 rows selected 

由此可见: 
Number类型的内部编码为:2 
根据每一行的len值可以看出,number是一个变长类型。不同的数值占用不同的空间。 
如果指定了精度,显示结果与精度相关。 
就像我插入语句写为 
insert into test_number values(0); 
但是显示结果为:0.00 
如果数值是负数,在最后一位上填充一个补码102.即表示该数值为负数。 
0是一个特殊的值,它在oracle中存储为128. 
第一位为标志位。以128为比较。如果数值大于128,则它大于0。如果小于128小于0。 
-1的内部存储为: 
-1.00 Typ=2 Len=3: 62,100,102 
最后一位是102,是一个负数。 
第一位小于128,所以小于10. 
除了第一位标志位外,其它的都是数值为了。 
如果该值是一个正数。每一位的存储值减1为每一位的实际值。 
1.0的存储结构为: 
1.00 typ=2 Len=2: 193,2 
实值上1.00的存储结果与1相同。 
第一位193为标志位,大于128,大于0. 
第二位为数值为,因为是正数,实际值为存储值减1。2-1 = 1。 
如是该值是一个负数,每一位的实际值为101 减去存储的值。 
-1.00的存储结构为: 
-1.00 Typ=2 Len=3: 62,100,102 
最后一位102为补位。 
第一位62为标志位,小于128。实际值小于0. 
第二位为数值为,因为是负数。实际值为:101 – 100  =1. 

§2.2 小数位在哪里? 
从上面的存储结果看,对小数存储时,它并没有一个小数的标志位。但是它实际上是由第一位标志位,和数值位(第二位)来决定的。 
当存储的数是一个正数,该数值的前几位为:第一位 * power(100 , (标志位 - 193)); 
当存储的数是一个负数,该数值的前几位为:第一位 * power(100,(62 – 标志位)); 
11.11的存储结果为: 
11.11 Typ=2 Len=3: 193,12,12 
第一位数值位为:12 实际数值为11 
标志位为:193 
12 * power(100, (193- 193); 
   100的零次方为1. 
12 乘1 等于12. 
所以这个数的前几位为:12。从这后面就是小数了。 
1234.12的存储结构为: 
1234.12 Typ=2 Len=4: 194,13,35,13 
第一位数值位为:13,实际值为12 
标志位为:193 
13 * power(100,(194-193)) = 1300 
所以前四位为整数位,后面的为小数位。 


-0.10的存储结构为: 
-0.10 Typ=2 Len=3: 63,91,102 
标志位为:63 
第一位数值为:91 ,实际值为:10 
91 * (100,(62-63)) =-9100. 
所以小数位在91之前。 

-1234.12的存储结构为: 
-1234.12 Typ=2 Len=5: 61,89,67,89,102 
标志位为:61 
第一位数值为:89 
89*(100,(62-61)) =8900 
所以小数位在67之后。 
§2.3 number的精度和小数位 
Number类型的精度最多可是38位。小数位-84--127位。 
SQL> create table test_number1(col_number number(39)); 

create table test_number1(col_number number(39)) 

ORA-01727: numeric precision specifier is out of range (1 to 38) 
指定小数位时,精度只能是1-38。不能是0 
SQL> create table test_number1(col_number number(0,127)); 

create table test_number1(col_number number(0,127)) 

ORA-01727: numeric precision specifier is out of range (1 to 38) 



SQL> create table test_number1(col_number number(1,128)); 

create table test_number1(col_number number(1,128)) 

ORA-01728: numeric scale specifier is out of range (-84 to 127) 

精度与小数位的关系。精度并不是小数位加整数位之和。 
我们先看看小数位为0的情况。 
SQL> create table test_number1(col_char varchar2(200), col_num number(10)); 

Table created 
Number(10).只定义了精度,小数位为0. 
看看它可以存放的数据。 


SQL> insert into test_number1 values('9999999999',9999999999); 

1 row inserted 

插入了10个9,没有问题,再插入多一位看看 
SQL> insert into test_number1 values('99999999991',99999999991); 

insert into test_number1 values('99999999991',99999999991) 

ORA-01438: value larger than specified precision allowed for this column 

报错了,精度不够。 
再看看能不能再插入小数? 
SQL> insert into test_number1 values('0.9',0.9); 

1 row inserted 


SQL> select * from test_number1; 

Col_char COL_NUM 
--------------------	-------------- 
9999999999	 9999999999 
0.9	             1 

注意插入数值0.9后,存储为1.这就是小数位的作用。在哪里进行舍入。 

带小数位和精度的情况。 
SQL> create table test_number2(col_char varchar(20),col_num number(1,3)); 

Table created 
精度是1,小数位是3. 
可见,精度不是小数位加整数位了。但是精度和小数位倒底什么关系呢? 
SQL> insert into test_number2 values('0.111',0.111); 

insert into test_number2 values('0.111',0.111) 

ORA-01438: value larger than specified precision allowed for this column 
插入3位小数,0.111竟然报错了,说精度不够。 
SQL> insert into test_number2 values('0.001',0.001); 

1 row inserted 
插入0.001时,成功了。 
SQL> insert into test_number2 values('0.001',0.0015); 

1 row inserted 
插入0.0015也成功了。 
看看插入的值。 
SQL> select * from test_number2; 

COL_CHAR             COL_NUM 
-------------------- ------- 
0.001                  0.001 
0.0015                 0.002 
需要注意的是0.0015被舍入为0.002 

精度大于小数位 
SQL> create table test_number3 (col_char varchar(20), col_number number(5,3)); 

Table created 

SQL> insert into test_number3 values('99.899',99.899); 

1 row inserted 

SQL> insert into test_number3 values('99.999',99.999); 

1 row inserted 

SQL> insert into test_number3 values('99.9999',99.9999); 

insert into test_number3 values('99.9999',99.9999) 

ORA-01438: value larger than specified precision allowed for this column 

注意,当插入99.9999时,系统报错。因为小数位为3位。第四位小数位是9,于是往前入。最终变成100.000.就已经超过了精度。 
Number(5,3)可存储的数值最大为99.999. 
现在终于有点明白小数位与精度的关系了。 

number(38,127) 
可以存储的最大小数为:127位小数,最后38为9. 
即:0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099999999999999999999999999999999999999

小数位为负数。 
我们从前面知道,小数位的取值为-48 ~127 
为什么小数位会为负数?这有点怪异了。像上面的number(5,3)将值舍入为最接近0.001 
Number(5,-3)就是将值舍入为最接近的1000 

SQL> create table test_number5 (col_char varchar(20), col_num number(5,-3)); 

Table created 

插入值10999 
SQL> insert into test_number5 values('10999',10999); 

1 row inserted 
查看一下结果 
SQL> select * from test_number5; 

COL_CHAR             COL_NUM 
-------------------- ------- 
10999                  11000 
存储的结果为:11000 
当小数部分为负数时,是对小数部分进行舍入。 

那么精度在这时起到什么作用呢?与小数位又有什么关系? 
SQL> insert into test_number5 values('111111111',111111111); 

insert into test_number5 values('111111111',111111111) 

ORA-01438: value larger than specified precision allowed for this column 
插入9个1时,报错精度不够。 
SQL> insert into test_number5 values('11111111',11111111); 

1 row inserted 
插入8个1时,正确插入。 
我们看看它的结果,看它是怎么舍入的。 
SQL> select * from test_number5; 

COL_CHAR             COL_NUM 
-------------------- ------- 
11111111             11111000 

结果是1111100而不是1111100 
无限接近1000,就是从百位开始进行四舍五入,后面的值全部为0。 
所以看出number(5,-3)可存储的最大值为:99999000 
SQL> insert into test_number5 values('99999499.999999',99999499.999999); 

1 row inserted 

SQL> select * from test_number5; 

COL_CHAR             COL_NUM 
-------------------- ------- 
99999999             99999000 
99999499.999999      99999000 

现在应该明白了精度和小数位的关系了吧。 
小数位告诉系统保留多少位小数,从哪里开始舍入。 
精度舍入后,从舍入的位置开始,数值中允许有多少位。 

§2.4  binary_float 和binary_double 
这两种类型是oracle 10g新引进的数值类型。在oracle 10g之前是没有这两种类型的。 
Number类型是由oracle软件支持的类型。而浮点数用于近似数值。但是它浮点数允许由在硬盘上(CPU,芯片)上执行运行。而不是在oracel进程中运算。如果希望在一个科学计算中执行实数处理,依赖于硬件的算术运算速度要快得多。但是它的精度却很小。如果希望用来存储金融数值,则必须用number. 
BINARY_FLOAT是一种IEEE固有的单精度浮点数。可存储6位精度,取值范围在~±1038.25的数值。 
BINARY_DOUBLE是一种IEEE固有的双精度浮点数。可存储12位精度。取值范围在~±10308.25的数值 
SQL> create table test_floatdouble(col_number number, col_float binary_float, col_double binary_double); 

Table created 

SQL> insert into test_floatdouble values(9876543210.0123456789,9876543210.0123456789,9876543210.0123456789); 

1	row inserted 
2	SQL> select to_char(col_number), to_char(col_float), to_char(col_double) from test_floatdouble; 
3	
4	TO_CHAR(COL_NUMBER)                      TO_CHAR(COL_FLOAT)                       TO_CHAR(COL_DOUBLE) 
5	---------------------------------------- ---------------------------------------- ---------------------------------------- 
6	9876543210.0123456789                    9.87654349E+009                          9.8765432100123463E+009 

由此可见,binary_float无法表示这个数。Binary_float和binary_double无法用于对精度要求高的数据。 



SQL> select dump(col_float)from test_floatdouble; 

DUMP(COL_FLOAT) 
-------------------------------------------------------------------------------- 
Typ=100 Len=4: 208,19,44,6 

BINARY_FLOAT 类型编码为100 
Len=4 占用4个字节。它是采用固定字节进行存储的。 

SQL> select dump(col_double)from test_floatdouble; 

DUMP(COL_DOUBLE) 
-------------------------------------------------------------------------------- 
Typ=101 Len=8: 194,2,101,128,183,80,25,73 

BINARY_DOUBLE 类型编码为101 
Leng= 8 占用8个字节。也是采用固定字节进行存储。 

注意:number 类型使用的CPU时间是浮点数类型的50倍。浮点数是数值的一个近似值,精度在6-12位之间。从Number类型得到的结果要比从浮点数得到的结果更精确。但在对科学数据进行数据挖掘和进行复杂数值分析时,精度的损失是可以接受的,还会带来显著的性能提升。 
这时需要使用内置CAST函数,对NUMBER类型执行一种实时的转换,在执行复杂数学运算之前先将其转换为一种浮点数类型。CPU使用时间就与固有浮点类型使用的CPU时间非常接近了。 
Select ln(cast(number_col as binary_double)) from test_number. 

§2.5 Oracle在语法上还支持的数值数据类型 
NUMERIC(p,s):完全映射到NUMBER(p,s)。如果p未指定,则默认为38. 
DECIMAL(p,s)或DEC(p,s):同NUMERIC(p,s). 
INTEGER或int:完全映射至NUMBER(38) 
SMALLINT:完全映射至NUMBER(38) 
FLOAT(b):映射至NUMBER 
DOUBLE PRECISION:映射到NUMBER 
REAL:映射到NUMBER. 

 

 

 

第三部分 日期时间类型 
§3.1  DATE 
Date类型Oralce用于表示日期和时间的数据类型。固定占用7个字节。 
包括七个属性: 
世纪 
世纪中的年份 
月份 
月份中的哪一天 
小时 
分 
秒 
SQL> create table test_date(col_date date); 

Table created 
SQL> insert into test_date values(to_date('2008-06-27 10:35:00','yyyy-mm-dd hh24:mi:ss')); 

1 row inserted 

SQL> select to_char(col_date,'yyyy-mm-dd hh24:mi:ss'),dump(col_date) from test_date; 

TO_CHAR(COL_DATE,'YYYY-MM-DDHH DUMP(COL_DATE) 
------------------------------ -------------------------------------------------------------------------------- 
2008-06-27 10:35:00            Typ=12 Len=7: 120,108,6,27,11,36,1 

Date类型的内部编码为12 
长度:占用7个字节 
数据存储的每一位到第七位分别为:世纪,年,月,日,时,分,秒。 
世纪:采用”加100”表示法来存储。即世纪+100来存储。120 – 100 = 20 
年:跟世纪一样采用”加100”表示法来存储。108 – 100 = 08(采用两位表示) 
月:自然存储.6 
日:自然存储,不做修改,27 
时:(时,分,秒都采用“加1”法存储)11 -1= 10 
分:36 -1 = 35 
秒:1 -1 = 0 
为什么世纪和年份要用加100法存储呢?是为了支持BC和AD日期。 
BC即为公元前。 
AD即为公元。 
如果世纪 – 100为一个负数,那么就是一个BC日期。 

插入一个公元前日期 
SQL> insert into test_date values(to_date('-4712-01-01','syyyy-mm-dd hh24:mi:ss')); 

1 row inserted 

SQL> select to_char(col_date,'bc yyyy-mm-dd hh24:mi:ss'),dump(col_date) from test_date; 

TO_CHAR(COL_DATE,'BCYYYY-MM-DD DUMP(COL_DATE) 
------------------------------ -------------------------------------------------------------------------------- 
公元 2008-06-27 10:35:00       Typ=12 Len=7: 120,108,6,27,11,36,1 
公元前 4712-01-01 00:00:00     Typ=12 Len=7: 53,88,1,1,1,1,1 
我们已经了解了日期的存储结构。当要对日期进行截取时,比如去掉时,分,秒。只需要把最后的三个字节设为:12 12 1就可以了。 
SQL> create table test_date1 (col_char varchar2(12), col_date date); 

Table created 

SQL> insert into test_date1 values('full',to_date('2008-06-27 12:01:00','yyyy-mm-dd hh24:mi:ss')); 

1 row inserted 
SQL> insert into test_date1(col_char,col_date) select 'minute', trunc(col_date,'mi') from test_date1 
  2     union all 
  3      select 'day', trunc(col_date,'dd') from test_date1 
  4     union all 
  5      select 'month',trunc(col_date,'mm') from test_date1 
  6      union all 
  7       select 'year',trunc(col_date,'y') from test_date1 
  8  ; 

4 rows inserted 
SQL> select col_char, col_date,dump(col_date) from test_date1; 

COL_CHAR     COL_DATE    DUMP(COL_DATE) 
------------ ----------- -------------------------------------------------------------------------------- 
full         2008-6-27 1 Typ=12 Len=7: 120,108,6,27,13,2,1 
minute       2008-6-27 1 Typ=12 Len=7: 120,108,6,27,13,2,1 
day          2008-6-27   Typ=12 Len=7: 120,108,6,27,1,1,1 
month        2008-6-1    Typ=12 Len=7: 120,108,6,1,1,1,1 
year         2008-1-1    Typ=12 Len=7: 120,108,1,1,1,1,1 

要把一个日期截取,只取到年。数据库只是把最后5个字节置上1。这是非常快的。 
当我们对一个Date字段进行操作,需要截取到年份进行比较时,我们经常使用to_char函数。通过会这样写。 
Select * from test_date1 where to_char(col_date ,’yyyy’) = ‘2008’ 
而不是 
Select * from test_date1 where trunc(col_date,’y’) = to_date(‘2008-01-01’,’yyyy-mm-dd’) 

使用trunc会占用更少的资源,性能更优。 
使用to_char所有的CPU时间与trunc相差一个数量级,差不多10倍。因为to_char必须把日期转换成一个串,并利用当前系统所采用的NLS来完成,然后执行一个串与串的比较。而TRUNC只需要把后5个字节设置为1,然后将两个7位的字节的二进行数进行比较就搞定了。所要截取一个DATE列叶,应该避免使用to_char. 
另外,要完全避免对DATE列应用函数。比如我们要查询2008年的所有数据,并且这一列上也有索引,我们希望能够用上这个索引。 
SQL> select count(col_date) from test_date1 where col_date >= to_date('2008-01-01','yyyy-mm-dd') and col_date < to_date('2009-01-01','yyyy-mm-dd'); 

COUNT(COL_DATE) 
--------------- 
              5 

§3.2 向Date类型增加或减时间 
怎么向Date类型增加时间,例如:向Date增加1天,或1小时,或1秒,一月等。 
常有的办法有几个方法: 
a.向date增加一个NUMBER值。因为Date 加减操作是以天为单位。1秒就是 1/24/60/60。依此类推。 
b.使用INTERVAL类型。后续会介绍 
c.使用内置函数add_months增加月。增加月不像增加天那么简单,所以需要使用内置函数来处理。 


3.2.1  增加秒 
SQL> create table test_date2(id varchar2(10), operate_time date); 

Table created 

SQL> insert into test_date2 values('1',sysdate); 

1 row inserted 

SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2008-06-27 13:35:35 
SQL> update test_date2 set operate_time = operate_time + 1/24/60/60 where id=1; 

1 row updated 

SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2008-06-27 13:35:36 

3.2.2 增加分 
SQL> update test_date2 set operate_time = operate_time + 1/24/60 where id=1; 

1 row updated 

SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2008-06-27 13:36:36 

3.2.3 增加小时 
SQL> update test_date2 set operate_time = operate_time + 1/24 where id=1; 

1 row updated 
SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2008-06-27 14:36:36 

3.2.4 增加天 
SQL> update test_date2 set operate_time = operate_time + 1 where id=1; 

1 row updated 
SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2008-06-28 14:36:36 

3.2.4 增加周 
SQL> update test_date2 set operate_time = operate_time + 1 * 7 where id=1; 

1 row updated 

SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2008-07-05 14:36:36 
3.2.5 增加月 
SQL> update test_date2 set operate_time = add_months(operate_time,1)  where id=1; 

1 row updated 
SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2008-08-05 14:36:36 
3.2.6 增加年 
SQL> update test_date2 set operate_time = add_months(operate_time,1 * 12)  where id=1; 

1 row updated 
SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2009-08-05 14:36:36 

另外可以使用一个非常有用的函数NUMTODSINTERVAL来新增加小时,分钟,秒。 
SQL> update test_date2 set operate_time = operate_time + numtodsinterval(1,'second') where id=1; 

1 row updated 
SQL> select id, to_char(operate_time, 'yyyy-mm-dd hh24:mi:ss') from test_date2 where id=1; 

ID         TO_CHAR(OPERATE_TIME,'YYYY-MM- 
---------- ------------------------------ 
1          2009-08-05 14:36:37 

Numtodsinterval(n, 'second') 获得秒的时间间隔 
Numtodsinterval(n, 'minute') 获得分的时间间隔 
Numtodsinterval(n, 'month') 获得月的时间间隔 
Numtodsinterval(n, 'year') 获得月的时间间隔 

增加月份时要非常的小心,应该使用add_months函数。为什么呢? 
比如当前日期为2000-2-29日。增加一个月得到的日期就应该是2000-3-31 
如果只是简单的加30天或加31天,是无法实现的。所以必须使用add_months函数,它会自动来处理这种月末问题。对年份进行增加也会出现类似的问题 

§3.3 TIMESTAMP 

TIMESTAMP是支持小数秒和时区的日期/时间类型。对秒的精确度更高。 

3.3.1 TIMESTAM语法 
TIMESTAMP(n) 
N的取值为0~9.表示指定TIMESTAMP中秒的小数位数。N为可选。如果n为0,timestamp与date等价。 
SQL> create table test_timestamp(col_date date, col_timestamp timestamp(0)); 

Table created 

SQL> insert into test_timestamp values(sysdate,systimestamp); 

1 row inserted 
SQL> select dump(col_date) from test_timestamp; 

DUMP(COL_DATE) 
-------------------------------------------------------------------------------- 
Typ=12 Len=7: 120,108,6,27,17,8,37 

SQL> select dump(col_timestamp) from test_timestamp; 

DUMP(COL_TIMESTAMP) 
-------------------------------------------------------------------------------- 
Typ=180 Len=7: 120,108,6,27,17,8,38 

如果指定了保留小数位数,那情况就大不一样了。 
SQL> create table test_timestamp1 (col_date date, col_timestamp timestamp(9)); 

Table created 

SQL> insert into test_timestamp1 values(sysdate, systimestamp); 

1 row inserted 
SQL> select dump(col_date) from test_timestamp1; 

DUMP(COL_DATE) 
-------------------------------------------------------------------------------- 
Typ=12 Len=7: 120,108,6,27,17,36,40 
SQL> select dump(col_timestamp) from test_timestamp1; 

DUMP(COL_TIMESTAMP) 
-------------------------------------------------------------------------------- 
Typ=180 Len=11: 120,108,6,27,17,36,40,17,249,15,24 

现在可以看到timestamp(9)占用了11个字节的空间。后面额外的四个字节包括了小数秒数。 

3.3.2 TIMESTAMP 相减 
将两个Date相减的结果是一个number.而将两个timestamp相减的结果是一个INTERVAL值 
SQL> create table test_timestamp2(time1 timestamp(9), time2 timestamp(9)); 

Table created 

SQL> insert into test_timestamp2 values(to_timestamp('2008-06-29 01:02:01.100000','yyyy-mm-dd hh24:mi:ss.ff'),to_timestamp('2008-07-29 02:03:02.000000','yyyy-mm-dd hh24:mi:ss.ff')) 
  2  ; 

1 row inserted 
SQL> select time2 - time1 from test_timestamp2; 

TIME2-TIME1 
--------------------------------------- 
+000000030 01:01:00.900000000 
结果表示两个时间之间相隔的天数,小时数,分数,秒数. 
相差30天1小时1分0.9秒 

有时我们需要得到两个时间之前相关的年数和月数. 
SQL> select numtoyminterval(months_between(time2,time1),'month') years_months, time2 - 
  2  add_months(time1 , trunc(months_between(time2,time1))) days_hours from test_timestamp2; 

YEARS_MONTHS                            DAYS_HOURS 
--------------------------------------- --------------------------------------- 
+000000000-01                           +000000000 01:01:01.000000000 

在计算时,分,秒间隔时我们注意到,使用add_months之后,小数秒就丢掉了. 
如果要保留集小数秒,我们就需要使用numtoyminterval函数 
SQL> select numtoyminterval(months_between(time2,time1),'month') years_months, time2 -(time1 + numtoyminterval(trunc(months_between(time2,time1)),'month')) day_hours from test_timestamp2; 

YEARS_MONTHS                            DAY_HOURS 
--------------------------------------- --------------------------------------- 
+000000000-01                           +000000000 01:01:00.900000000 


§3.4  TIMESTAMP WITH TIME ZONE 
  TIMESTAMP WITH TIME ZONE类型是TIMESTAMP的子类型,增加了时区支持。 
SQL> create table test_timezone(col_ts timestamp, col_tz timestamp with time zone); 

Table created 
SQL> insert into test_timezone values(systimestamp, systimestamp); 

1 row inserted 
SQL> select dump(col_tz) from test_timezone; 

DUMP(COL_TZ) 
-------------------------------------------------------------------------------- 
Typ=181 Len=13: 120,108,6,27,9,55,24,43,209,96,112,28,60 

SQL> select dump(col_ts) from test_timezone; 

DUMP(COL_TS) 
-------------------------------------------------------------------------------- 
Typ=180 Len=11: 120,108,6,27,17,55,24,43,209,96,112 

占用13字节的存储空间,最后两位用于保存时区信息。 
在timestamp类型中,对时,分,秒的存储采用了加1法。 
在timestamp with time zone上执行timestamp运算时,oracle自动把两个类型首先转换为UTC时间,然后再执行运算。 

§3.5 TIMESTAMP WITH LOCAL TIME ZONE 
这个类型保存进数据库时会先转换成数据库时区再进行保存. 
SQL> create table test_timeltz(col_date date, timetz timestamp with time zone, timeltz timestamp with local time zone); 

Table created 

SQL> insert into test_timeltz values(timestamp'2008-06-29 12:03:22.111 US/Pacific',timestamp'2008-06-29 12:03:22.111 US/Pacific',timestamp'2008-06-29 12:03:22.111 US/Pacific'); 

1 row inserted 
SQL> select dbtimezone from dual; 

DBTIMEZONE 
---------- 
+08:00 

SQL> select * from  test_timeltz; 

COL_DATE    TIMETZ                                                                           TIMELTZ 
----------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 
2008-6-29 1 2008-06-29 12:03:22.111000 US/PACIFIC                                            2008-06-30 03:03:22.111000 
SQL> select dump(col_date), dump(timetz), dump(timeltz) from test_timeltz; 

DUMP(COL_DATE)                                                                   DUMP(TIMETZ)                                                                     DUMP(TIMELTZ) 
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 
Typ=12 Len=7: 120,108,6,29,13,4,23                                               Typ=181 Len=13: 120,108,6,29,20,4,23,6,157,185,192,137,156                       Typ=231 Len=11: 120,108,6,30,4,4,23,6,157,185,192 


请注意: 
第一列,类型为date,只存储了日期和时间.时区和小数秒已经丢失了.不会执行时区转换. 
第二列:类型为timestamp with time zone.保留了时区信息.并规范化了该时区相应的UTC时间. 
第三列:类型为timestamp with local time zone.进行了转换,把插入的时间转为了数据库时区的时间. 

timestamp with local time zone也是不需要记录时区信息的.它占用7-11个字节. 
一旦你的数据表中有一列使用了timestamp with local time zone,你就不能再改变数据库时区. 

通用协调时(UTC, Universal Time Coordinated) ,UTC与格林尼治平均时(GMT, Greenwich Mean Time)一样,都与英国伦敦的本地时相同. 北京时区是东八区,领先UTC八个小时 

§3.6  INTERVAL 
用于表示一段时间或一个时间间隔的方法.在前面有多次提过.INTERVAL有两种类型. 
YEAR TO MONTH 能存储年或月指定的一个时间段. 
DATE TO SECOND存储天,小时,分钟,秒指定的时间段. 
在前面用到的两个函数numtoyminterval 和numtodsinterval就是创建interval最好的方法. 
另外extract 函数可以很容易的获得时间的每个部分. 
SQL> select extract(day from  time2-time1) day, extract(hour from time2 - time1) hour, 
  2  extract (minute from time2 - time1) minute,extract (second from time2 - time1) second from 
  3  test_timestamp2; 

       DAY       HOUR     MINUTE     SECOND 
---------- ---------- ---------- ---------- 
        30          1          1        0.9 

3.6.1 Interval year to month 
  语法: 
Interval year(n) to month 
N表示年数的位数.取值:0~9 .默认为2,表示年数为0 ~ 99 

如果要产生一个1年2个月的时间段.用numtoyminterval是最方便的. 
SQL> select (numtoyminterval(1,'year') + numtoyminterval(2,'month')) yminterval from dual; 

YMINTERVAL 
--------------------------------------- 
+000000001-02 

或者是: 
SQL> select  numtoyminterval(1 * 12 + 2,'month') yminterval from dual; 

YMINTERVAL 
--------------------------------------- 
+000000001-02 

另外可以使用 to_yminterval(‘1-2’)函数来进行转换. 
SQL> create table test_intervarym(col_interval interval year to month); 

Table created 
SQL> insert into test_intervarym values ( numtoyminterval(1 * 12 + 2,'month')); 

1 row inserted 
SQL> select * from test_intervarym; 

COL_INTERVAL 
--------------------------------------- 
+01-02 

SQL> select dump(col_interval) from test_intervarym; 

DUMP(COL_INTERVAL) 
-------------------------------------------------------------------------------- 
Typ=182 Len=5: 128,0,0,1,62 

INTERVAL year to month 采用固定5个字节进行存储.最后一位为天数值.采用加60算法.所以计算是需要减去60. 
第一位为标志位,标志是否为正负数. 
第二到第四位表示年数. 
第五位表示日数 

3.6.2 INTERVAL DAY TO SECOND 
  定义如下: 
  INTERVAL DAY(n) to second(m) 
  N为可选位数,表示天数的位数.可取值0~9,默认为2位. 
  M是秒字段小时的位数.取值0~9,默认为6 

SQL> create table test_intervalds(col_ds interval day(9) to second(9)); 

Table created 
SQL> insert into test_intervalds values(numtodsinterval(1,'second')); 

1 row inserted 
SQL> insert into test_intervalds values(numtodsinterval(1.000000001,'second')); 

1 row inserted 
SQL> select col_ds, dump(col_ds) from test_intervalds; 

COL_DS                                  DUMP(COL_DS) 
--------------------------------------- -------------------------------------------------------------------------------- 
+000000000 00:00:01.000000000           Typ=183 Len=11: 128,0,0,0,60,60,61,128,0,0,0 
+000000000 00:00:01.000000001           Typ=183 Len=11: 128,0,0,0,60,60,61,128,0,0,1 

可见,这种类型也是采用固定11个字节来存储的. 
第一位为标志位,区分正负数 
第二到第四位表示天数. 
第五位表示小时数.时,分,秒采用加60算法 
第六位表示分钟数, 
第七位表示秒数. 
最后四位表示小数秒数. 

 

 

 

第四部分	LOB类型 
§ 4.1  LOB类型 
4.1.1 LOB类型分类 
CLOB:字符LOB.用于存储大量的文本信息.采用默认字符集存储 
NCLOB:用于存储字符LOB,采用数据库的国家字符集来存储字符.而不是数据库的默认字符集. 
BLOB:二进制LOB,存储二进大量的二进制信息.存储时不会进行字符集转换. 
CLOB和BLOG在ORACLE 10G中可存储8TB字节. 
BFILE:二进制文件LOB,只是一个文件指针.具体的文件存储在操作系统中. 

4.1.2 LOB类型存储方式 

我们把CLOB,NCLOB,BLOB存储在数据库的内部称为内部LOB.这些存储方式都相似,所以可以一起进行讨论. 

SQL> create table test_lob (id int primary key,remark clob); 

Table created 

对于LOB列的创建有非常多的选项.可以查ORACLE文档. 
最简单的就是使用dbms_metadata来获得它的完整的脚本. 
select dbms_metadata.get_ddl('TABLE','TEST_LOB') from dual; 

得到如下结果 
  CREATE TABLE "YUAN"."TEST_LOB" 
   (	"ID" NUMBER(*,0), 
"REMARK" CLOB, 
PRIMARY KEY ("ID") 
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) 
  TABLESPACE "USERS"  ENABLE 
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING 
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) 
  TABLESPACE "USERS" 
LOB ("REMARK") STORE AS ( 
  TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 
  NOCACHE LOGGING 
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)) 

LOB列的定义可以有以下属性. 
存储的表空间,本例为USER.也就是说可以为LOB单独指定表空间. 
ENABLE STORAGE IN ROW 默认的一个属性 
CHUNK 属性 
PCTVERSION 属性 
NOCACHE 属性. 
一个完整的STORAGE语句. 

可见,LOB类型之前介绍的数据类型相比要复杂得多了. 
当我们创建了一个带的LOB列的表后,我们可以从USER_SEGMENTS查到,数据库增加了几个段对象. 
SQL> select segment_name,segment_type from user_segments; 

SEGMENT_NAME                      SEGMENT_TYPE 
--------------------------------- ------------------ 
BIN$nZwCJWDmQM+ygfB1U8tcIw==$0    TABLE 
BIN$0jfW0nNQR/2JEQmbAmfcRQ==$0    TABLE 
TEST_TIMESTAMP                    TABLE 
TEST_TIMESTAMP2                   TABLE 
TEST_TIMESTAMPWZ                  TABLE 
TEST_TIMELTZ                      TABLE 
TEST_INTERVARYM                   TABLE 
TEST_INTERVALYM2                  TABLE 
TEST_INTERVALDS                   TABLE 
TEST_LOB                          TABLE 
SYS_LOB0000043762C00002$$         LOBSEGMENT 
SYS_IL0000043762C00002$$          LOBINDEX 
SYS_C004324                       INDEX 

后面四个段空间对象.新增了四个物理段.普通表只会新增一个或两个段对象.类型为TABLE和INDEX. 
而LOB列则额外新增了两个段对象,类型为LOBSEGMENT和LOBINDEX. 
SYS_C004324是一个索引段,因为我们有一列为主键. 
作为普通字段,数据就存放在表段中.索引就放在索引段中. 
而对于LOB数据,数据并不是存在表段中,而是存放在LOBSEGMENT段中.(有些情况下是存放在表test_lob中的.后面会讲) 
LOBINDEX用于指向LOB段,找出其中的某一部分. 
所以存储在表中的LOB存储的是一个地址,或者说是一个指针,也可以说是一个LOB定位器(LOB locator). 
存储在LOBindex中的应该是每一个LOB行的地址.数据是具体存储在LOBSEGMENT中的. 
我们先从TEST_LOB的LOB列中找到一个地址,然后在LOBINDEX中来查找这些字节存储在哪里.然后再访问LOBSEGMENT.由此我们可以把lobindex和lobsegment想成是一个主/细表的关系. 


实际上lob列中存的是一个地址段.然后在lobindex找到所有的地址段.然后在lobSegment中把所有地址段的值都读取了来 

4.1.3 LOB类型存储参数介绍 
在此,我们已经基本了解了LOB是怎么存储的.我们也从脚本中看到了LOB类型的参数.现在我们就来了解这些参数 
1.	LOB表空间 
LOB ("REMARK") STORE AS ( 
  TABLESPACE "USERS" 
在test_lob表中的create语句中包含上面的语句.这里指定的表空间指的是存储lobindex 和lobsegment的表空间.也就是说,存放lob数据与LOB列所在的表是可以在不同的表空间的. 
数据表和LOB存放在不同的表空间. 
为什么LOB数据会放在不同的表空间呢?这主要还是管理和性能的问题. 
LOB数据类型代表了非常巨大的容量.在ORACLE 10G之前,LOB列可以存放4GB字节的数据.在ORACLE 10G 中LOB类型可以存放8TB字节的数据.这是非常庞大的数据. 
所以就有必要为LOB数据使用一个单独的表空间,对于备份和恢复以及空间管理.你甚至可以让LOB数据使用另外一个区段大小,而不是普通表数据所用的区段大小. 
  另外从I/O性能的角度考虑.LOB是不在缓冲区缓存中进行缓存.因此每个LOB的读写,都会产生物理I/O.正因为如此,如果我们很清楚在实际的用户访问中,有些对象会比大部分其它对象需要花费更多的物理I/O,那么就需要把这些对象分离到其它的磁盘. 
另外,lobindex 和lobsegment是在同一个表空间中的.不可以把lobindex和lobsegment放在不同的表空间中.在oracle 8i之前版本,允许将lobindex和lobsegment放在不同的表空间中. 
2.	IN ROW 语句 
LOB ("REMARK") STORE AS ( 
  TABLESPACE "USERS" ENABLE STORAGE IN ROW 
我们已经了解了LOB类型的存储结构,但是这种结构会带来额外的磁盘访问.不管是读还是写都会比普通数据类型要慢及带来更多的物理I/O. 
针对这种情况,ORALCE作出了个改进就是IN ROW 语句. 
使用ENABLE STORAGE IN ROW从字面上理解就是允许行内存储.当LOB的内容小于4000字节时,就把数据存储在数据表中的,即LOB数据与数据表都是同一个表空间中.这里的LOB就相当于VARCHAR2一样,这里LOB列的数据还可以进入缓冲区进行存储.当LOB内容超过了4000字节后,就会把数据移到lobsegment中去. 
当定义一个LOB列时,它的大小一般都是小于4000字节的,启用IN ROW 是非常重要的. 
如果要禁用IN ROW ,就使用DISALBE STORAGE IN ROW 
3.	CHUNK 参数 
LOB ("REMARK") STORE AS ( 
  TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 

CHUNK 意为大块,块.是指LOB存储的单位.指向LOB数据的索引会指向各个数据块.CHUNK是逻辑上连续的一组数据块.CHUNK是指LOB的最小分配单元.而数据库的最小内存分配单元是数据块(BLOCK).CHUNK大小必须是ORACLE块大小的整数倍. 
我们先来了解一下LOB与CHUNK的关系. 
1.	每一个LOB实例(即每一行的LOB值)会至少占用一个CHUNK. 
用我们本节的数据表test_lob为例,remark列为LOB类型. 
假设该表有1000行数据,每一行的remark列的值大小都为7KB. 
这样数据库就会分配1000个CHUNK.如果CHUNK的大小设置是64KB,就会分配1000个64KB的CHUNK.如果CHUNK的大小为8KB,就分配1000个8KB的CHUNK. 
重要的一点就是一个CHUNK只能由一个LOB对象使用.这有一点像CHAR这种定长类型.如果把CHUNK设为64KB,而实际上我们每一个LOB对象只有7KB的大小,每一列浪费57KB的空间.1000列就浪费了55M的空间.而把CHUNK设为8KB,1000列大约浪费1M的空间. 
我们还知道lobindex,且于指向各个块.它会记录每个块的地址.所以当块越多时,索引就越大,索引越大时,读写就会更慢.整体的性能就会降低. 
比如每个列的LOB字段实际值大约8M,使用8KB的CHUNK.那么就需要1024个CHUNK.那么在lobindex中就会有1024条记录,用来指向这些CHUNK. 
指定CHUNK值,影响到性能和空间. 
如果CHUNK过大,就会白白浪费存储空间,如果CHUNK过小,就会降低性能. 
所以我们需要在空间和性能上进行取舍和折中. 

4.	PCTVERSION 语句 
LOB ("REMARK") STORE AS ( 
  TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 
PCTVERSION用于控制LOB的读一致性.普通字段都会有UNDO记录的.而lobsegment是没有undo记录的.而是直接在lobsegment本身中维护停息的版本.lobindex会像其它段一样生成undo记录.但是lobsegment不会. 
修改一个LOB对象时,oracle会分配一个新的CHUNK,而来的CHUNK会被保留下来.如果事务正常的提交了,lobindex就像指向新的CHUNK.如果事务被回滚了,lobindex就再指回原来的CHUNK.所以undo维护是在LOB段自身中实现的. 
这样一来,就会有非常多的无用的CHUNK被开销了.这也是非常大的空间损耗.这些CHUNK指的是数据的旧版本信息.那如何来控制这些旧版本数据占用的空间呢?这就是PCTVERSION的作用.也就是说用多少额外的空间来存储旧版本数据.我们可以看到默认的值是10%.如果你确实经常修改LOB,那么就需要把它设为10%就不够了,需要增加这个值. 

5.	CACHE参数 
LOB ("REMARK") STORE AS ( 
  TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 
  NOCACHE 
除了NOCACHE外,这个选项还可是CACHE和CACHE READS.这个参数控制lobsegment数据是否存储在缓冲区的缓存中.默认为NOCACHE,也就是每次访问都是从磁盘直接读取写. 
CACHE READS允许缓存从磁盘读的LOB数据.但是写入LOB数据是直接写进磁盘的. 
CACHE则是允许读和写都能缓存LOB数据. 

有些情况下,LOB字段只有几KB大小,进行缓存就非常有用了.如果不缓存,当用户更新LOB字段时,还必须进行等待,从磁盘直接读数据和写数据. 
如果要修改缓存设置可以用下面的语句 
ALTER TABLE test_lob modify LOB(remark) (CACHE); 
ALTER TABLE test_lob modify LOB(remark) (NOCACHE); 
ALTER TABLE test_lob modify LOB(remark) (CACHEREADS); 
但是对于大数据量的LOB读写,比如超过了20M.是没有理由把它放进缓存的 

§ 4.2 BFILE 
  BFILE类型只是操作系统上一个文件的指针.用于对存储在操作系统中的文件提供只读访问. 
使用BFILE时,还可以使用一个DIRECTORY 对象.DIRECTORY 是将一个操作系统目录映射到数据库的一个串.以便于提供可移值性. 
SQL> create table test_bfile(id int primary key, moviefile bfile); 

Table created 

SQL> create or replace directory movie_directory as 'D:/movie'; 

Directory created 
SQL> insert into test_bfile values(1,bfilename('movie_directory','英雄.dat')); 

1 row inserted 

对BFILE的操作需要使用DBMS_LOB包来进行.提供了一系统方法和函数 

 

 

 

第五部分 LONG类型 
LONG是一种已经被弃用的数据类型,LOB类型是它的替代品.所以留在LOB之后进行讨论. 
我们只需要简单的了解即可.为什么ORACLE还保留这种类型,只是为了向后兼容,在新的数据库设计是,不要再使用LONG类型列. 
LONG类型有两种: 
   LONG :能存储2GB的字符 
   LONG RAW:能存储最多2GB的二进制数据. 

我们只需要对LONG类型的限制进行了解即可. 


LONG/LONG RAW 类型	CLOB/BLOB类型 
一个表只能有一个LONG/LONG RAW列	一个表可以有最多1000个LOB类型列 
不能用于用户自定义类型	可以用于用户自定义类型 
WHERE中不能引用LONG类型	可以 
除了NOT NULL,完整性约束中不能引用	可以 
不支持分布式事务	支持 
不能使用基本或高级复制技术	可以 
不能在GROUP BY,ORDER BY,CONNECT BY,DISTINCT,UNIQUE,INTERSECT,MINUS,UNION中使用	可以通过函数来转换成一个标量SQL类型来支持 
PL/SQL函数和过程中不能作为参数	可以 
不能应用于内置函数,如SUBSTR	可以 
CREATE TABLE AS SELECT不能使用LONG类型	支持 
在有LONG类型的表中不能进行移动表空间	可以 
总之一句话,新系统不应该再使用LONG类型. 
老系统如果有的表的某些字段是LONG类型,要注意它的限制 

 

 

 

第六部分	ROWID 
ROWID 就是数据库中一行的地址,用于记录数据存储的一些属性,包括:记录存储所在的数据文件(file#),所属的数据库对象(obj#),所在的数据块号(block_no#),以及在表中的行号。这些属性就构成了Oracle 的ROWID. 
我们需要注意的是在数据表中并没有一列来专门记录ROWID。 
另外还有一个UROWID,它用于表,是行主键的一个表示,基于主键生成.一般是索引组织表在使用。索引组织表是没有ROWID的。 
不管是ROWID还是UROWID,数据表都没有专门的一列来记录。 
我们把这两种类型称为伪列。 
SQL> create table test_rowid (id number(38)); 

Table created 

SQL> insert into test_rowid values(1); 

1 row inserted 
SQL> select rowid, id from test_rowid; 

ROWID                                                   ID 
------------------ --------------------------------------- 
AAAKsAAAEAAAAC+AAA                                       1 

因为ROWID可以唯一的标识一条记录,所以索引中存储了ROWID值,通过索引访问记录,其实也就是通过从索引获得ROWID,再根据ROWID定位数据表中的记录。 
但是当对表进行分区移动之后,索引就需要重建,因为存储位置已经发生了变化,索引中的ROWID已经不能再定位到新的数据了。 
ORACLE 的ROWID一直在不断变化。 
在ORACLE 6中,ROWID使用6bit来表示文件号。 
在ORACLE 8,ROWID的组成是FFFF.BBBBBBBB.RRRR。占用6个字节。 
10bit 的file#,22bit的block#,16bit的row # 
在ORACLE 9中,Oracle 为ROWID引入了数据对象号的概念dataobj#. 
现在ROWID格式变为OOOOOO.FFF.BBBBBB.RRR。最新的ROWID采用Base64编码,一共有18位,代表80位二进制数,其中:O为数据对象号,F是文件号,B是块号,R是行号 
32 bit dataobj#+10bit rfile#,+22 bit block# +16bit row# 

在以前ROWID是保持不变的,但现在ROWID是会发生改变的。如: 
把一行从一个分区移到另一个分区 
使用闪回表(flashback table)命令将一个数据表恢复到以前的某个时间点 
对分区进行操作,如:移动,分解和合并 
对段进行收缩 
这些操作都会使ROWID发生变化,所以我们不应该把ROWID来作为唯一标识。而是使用一个单独的列为主键用来作数据行的唯一标识。另外主键约束可以实现引用完整性。而ROWID是无法做到的。 

笔者曾经使用ROWID排序来实现按数据的写入顺序来显示数据。这在大多数情况下是可以做的,但是如果以后因为维护数据库,对分区进行操作后,这样做是不可行的。 
所以应该使用单独的列来记录数据的写入顺序。 
ROWID类型的主要用途

  


  
分享到:
评论

相关推荐

    oracle数据类型及存储方式

    关于oracle数据类型及存储方式基础知识,也许对你有所帮助

    oracle数据类型及存储方式.doc

    RT oracle数据类型及存储方式.doc

    Oracle 数据类型及存储方式.pdf

    Oracle 数据类型及存储方式 数据存储是数据流在加工过程中产生的临时文件或加工过程中需要查找的信息。数据以某种格式记录在计算机内部或外部存储介质上。

    [整理版]oracle数据类型及存储方式.doc

    [整理版]oracle数据类型及存储方式

    oracle的数据类型及存储方式 文档

    oracle的数据类型及存储方式 文档,好资源和大家一起分享!

    Oracle基本数据类型存储格式浅析

    Oracle基本数据类型存储格式浅析(一)——字符类型 Oracle基本数据类型存储格式浅析(二)——数字类型 Oracle基本数据类型存储格式浅析(三)——日期类型 Oracle基本数据类型存储格式浅析(四)——ROWID类型 ...

    Oracle存储过程-1

    让你对oracle数据类型有一个全新的认识。揭示一些不为人知的秘密和被忽略的盲点。从实用和优化的角度出发,讨论每种数据类型的特点。从这里开始oracle之旅! 第一部份 字符类型 §1.1 char 定长字符串,会用空格...

    Oracle 数据类型

    5、DATE数据类型,使用7个字节固定长度,每个字节分别存储世纪,年,月,日 ,时,分,秒,ORACLE中SYSDATE函数的功能是返回当前的日期和时间 6、TIMESTAMP数据类型,和DATE相似,但是这个类型的秒精确到小数点后6...

    oracle基本数据类型存储格式浅析.pdf

    oracle 的基本数据类型的存储格式有了一些了解,最近有做了一些测试进行了验证。打算整理总结一下,这一 篇主要说明字符类型的存储格式。主要包括char、varchar2 和long 等几种类型。

    Oracle数据库之C#中调用示例Oracle自定义类型(自定义Object对象)

    C#调用Oracle自定义类型存储过程,Oracle存储入参为type类型,对于不熟悉Oracle朋友,可以参考来调用。实现思路和正常调MSSQL一样,先建立连接,再定义一个IOracleCustomType的类,然后Oracle.DataAccess.dll的...

    ORACLE中的数据类型.doc

    ORACLE中的数据类型.doc 当你在数据库中创建数据表的时候,你需要定义表中所有字段的类型。ORACLE有许多种数据类型以满足你的需要。数据类型大约分为:character, number, date, LOB, 和RAW等类型。虽然ORACLE8i也...

    oracle支持的数据类型

    常用的数据库字段类型如下: 字段类型 中文说明 限制条件 其它说明 CHAR 固定长度字符串 ...数据类型 参数 描述 char(n) n=1 to 2000字节 定长字符串,n字节长,如果不指定长度,缺省为1个字节长(一个汉字为2字节)

    【数据安全】Oracle 透明数据加密(TDE) 完整操作手册

    Oracle 透明数据加密 (TDE) 能够加密存储在表和表空间中的敏感数据,例如手机号码,身份证号等,对于有权访问数据的数据库用户或应用程序,加密数据将被透明地解密。 TDE 可在存储介质或数据文件被盗时保护存储在...

    sql server中的image类型的数据导出到oracle的clob字段中

    sql server中的image类型的数据导出到oracle的clob字段中

    Oracle试卷及答案

    1、 段是表空间中一种逻辑存储结构,以下(D)不是ORACLE数据库使用的段类型。 (A) 索引段 (B)临时段 (C)回滚段 (D)代码段 2、 ORACLE数据库物理结构包括以下三种文件,以下不属于的是(A) (A) 系统...

    21天学会oracle

    第13章 Oracle数据类型.ppt 第14章 Oracle中的函数与表达式.ppt 第15章 Oracle中的控制语句.ppt 第16章 SQL查询.ppt 第17章 SQL更新数据.ppt 第18章 数据库速度优化与数据完整性.ppt 第19章 数据一致性与事务管理....

    .net C# ORACLE带表参数存储过程

    因为.net中的除string类的数据类型外,其它类型要求手动修改UDT自动生成的类文件,本人测试了2天后,才明白要修改UDT自动生成的类文件,此功能特好用,尤其是象财务凭证中子栏的一次更新,要求一次将整张凭证更新到ORACLE,...

    oracle常用数据类型说明

    类型 含义 存储描述 备注 CHAR 固定长度字符串 最大长度2000bytes   VARCHAR2 可变长度的字符串, 最大长度4000bytes 可做索引的最大长度749 NCHAR 根据字符集而定的固定长度字符串 最大长度2000bytes   ...

    Oracle 从入门到精通视频教程(11G版本)(ppt)

    Oracle 11g中支持的数据类型 数据库定义语言(DDL) 约束的使用 数据操纵语言(DML) 第5章-利用SELECT检索数据 SQL-数据库沟通的语言标准 Oracle 11g中支持的数据类型 数据库定义语言(DDL) 约束的使用 ...

Global site tag (gtag.js) - Google Analytics