Oracle Number类型的讨论

本文探讨了Oracle数据库中NUMBER类型字段未指定精度可能导致的空间浪费问题,通过实例对比不同精度设置下的存储差异,并提供了检查现有数据库中NUMBER字段的脚本。

Specify scale for NUMBERs

Many developers do not specify a precision for NUMBER columns. This may be in order to minimize the work that would be needed to increase that precision should the application's requirements change, but it is often just a matter of habit. And although it is possible using the undocumented (*,scale) syntax to specify a scale without a precision, this is not normally done. If no precision and no scale are specified for an Oracle NUMBER column then it can contain arbitrary floating point numbers, and floating point numbers can be large.

In most cases this has no performance impact because the columns in fact only contain integers and the number of bytes required to store an integer is no more than one plus half the number of significant digits (more). However, large floating point values may be stored in these columns if their values are sometimes computed using floating point arithmetic. In particular, when saving the results of floating point arithmetic to a NUMBER column, if there is no scale, or if the scale is further to the right than the least significant digit of precision in the result, then rounding will not be performed (more) and thus the full storage precision will be used.

Here is an example. We will create a table with two NUMBER columns. The first will have no scale. The other will have a scale of 4 digits. Note the difference in storage space required when we then insert the same value to these two columns. Although the values are the same, the column without any scale takes 21 bytes to store the number, whereas the column with a small scale takes just 2 bytes to store the same number.

SQL> create table numbers (n1 number, n2 number(10,4));

Table created.

SQL> insert into numbers values (3*(1/3), 3*(1/3));

1 row created.

SQL> select * from numbers;

N1 N2
---------- ----------
1 1

1 row selected.

SQL> select vsize(n1), vsize(n2) from numbers;

VSIZE(N1) VSIZE(N2)
---------- ----------
21 2

1 row selected.

The value inserted was expressed as 3*(1/3) for the illustration above to force the use of floating point arithmetic during the statement execution. The brackets were needed to prevent the optimizer from simplifying the expression before execution. The example below uses the log function to illustrate the same point and to show that the scale must be to the left of the least significant digit of precision to ensure that the results of floating point expression evaluation are rounded prior to storage. Unless this rounding occurs, the full precision of the column will be used for data storage.

SQL> create table numbers (n1 number, n2 number(*,38), n3 number(*,37));

Table created.

SQL> insert into numbers values (log(2, 4), log(2, 4), log(2, 4));

1 row created.

SQL> select * from numbers;

N1 N2 N3
---------- ---------- ----------
2 2 2

1 row selected.

SQL> select vsize(n1), vsize(n2), vsize(n3) from numbers;

VSIZE(N1) VSIZE(N2) VSIZE(N3)
---------- ---------- ----------
21 21 2

1 row selected.

In this case because the value 2 has its most significant digit one place to the left of the decimal point, one byte is needed for digits to the left of the decimal point leaving 19 bytes for digits to the right of the decimal point. These 19 bytes can hold at most 38 digits, of which the last might not be accurate. Thus the scale must be no greater than 37 digits right of the decimal point to ensure rounding prior to storage. For larger values a smaller scale would be needed.

Because most Oracle NUMBER columns just store integers and are never subject to floating point arithmetic, this waste of space is not very widespread despite that developers often fail to specify the scale of NUMBERs. However, where it does occur it reduces table data density which in turn increases table scan I/O, uses cache memory less effectively, and reduces index efficiency. Therefore, it is good to be in the habit of always specifying at least a scale for real NUMBERs, if not a precision as well. For integers, if a precision is specified then a scale of 0 is implied, otherwise the scale should be set to zero explicitly using the NUMBER(*,0) datatype specification or a synonymous ANSI datatype specification.

Our unscaled_numbers.sql script can be used to check an existing database for columns that are wasting space because of this issue and therefore might be impacting performance. By default it identifies NUMBER columns for which no scale has been specified and which have an average data length of more than 9 bytes (more than 15 digits).

 
<think>我们正在讨论的是Oracle中将数字类型转换为字符串类型的方法。 根据引用[1]和引用[2]提供的信息,我们知道Oracle中可以使用TO_CHAR函数来将数字转换为字符串,并且可以指定格式。 引用[1]提到:将字符串类型的转换成数字类型使用to_number(str),那么反过来,数字转字符串则使用TO_CHAR。 引用[2]则具体说明了如何将数字格式化为字符串,特别是保留小数位数的问题。 因此,我们可以使用TO_CHAR函数,其基本语法为: TO_CHAR(numeric_value, [format_mask], [nls_parameter]) 其中: numeric_value:要转换的数字 format_mask(可选):格式模型,用于指定转换后的字符串格式 nls_parameter(可选):指定影响转换的NLS参数,如小数点字符和组分隔符 如果没有指定格式,TO_CHAR函数会将数字转换为一个足够表示该数字的字符串,但不会包含额外的格式(如小数部分如果为0则不会显示小数位,或者不会按固定小数位显示)。 引用[2]中提到的问题:如果数据库中存储的是number(20,2),即固定两位小数,那么直接取出时,0会显示为0(而不是0.00),2.1显示为2.1(而不是2.10),而3.88显示为3.88。如果我们希望保留两位小数,即和数据库中存储的格式一致(0显示为0.00,2.1显示为2.10),那么就需要使用TO_CHAR并指定格式。 常用的数字格式元素: - 9:代表一位数字(如果该位没有数字则不显示0) - 0:代表一位数字,如果该位没有数字则显示0 - .:小数点 - ,:千位分隔符 - FM:去除转换结果中的前导空格(因为TO_CHAR转换数字为字符串时,对于正数会在前面留一个空格给符号位,使用FM可以去掉这个空格以及前导0) 因此,将数字转换为字符串并保留两位小数,我们可以使用: TO_CHAR(number_value, '99999999999999999999.00') -- 这里整数部分根据实际位数调整,但这样写不够灵活 或者更通用的写法:TO_CHAR(number_value, 'FM999999999999990.00') -- 使用FM可以去掉前导空格,整数部分用9和0组合确保整数部分不会丢失前导0(但通常整数部分我们不需要前导0,所以用9即可,但要注意如果整数部分位数很多,需要写足够多的9) 但是,如果整数部分很长,我们可能会写很多个9,这样不方便。另一种方法是使用0来强制显示前导零,但通常我们不希望整数部分有前导零(除非有特殊需求)。所以,我们更关心的是小数部分。 所以,对于固定两位小数,我们可以这样写: TO_CHAR(number_value, 'FM999999999999999.00') -- 其中整数部分的9的个数根据实际需要设置,确保能覆盖最大整数位数。 但是,如果整数部分位数不确定,且可能很大,我们可以考虑使用科学计数法或者接受整数部分不固定,但这样可能不满足要求。 另外,如果我们不需要固定的小数位数,只是原样输出(即去掉末尾多余的0),那么我们可以使用: TO_CHAR(number_value) -- 这样转换,对于整数就不会显示小数部分,对于小数会去掉末尾的0(但会保留至少一位小数,如果小数部分不为0的话) 但是,根据引用[2]中的需求,他们希望保留两位小数,即使小数部分为0也要显示两位小数。所以,我们需要使用格式模型。 例如: SELECT TO_CHAR(0, 'FM9999999990.00') FROM dual; -- 返回 '0.00' SELECT TO_CHAR(2.1, 'FM9999999990.00') FROM dual; -- 返回 '2.10' SELECT TO_CHAR(3.88, 'FM9999999990.00') FROM dual; -- 返回 '3.88' 注意:格式模型中的整数部分,我们使用了0来确保至少显示一位整数(即使为0)。如果整数部分为0,那么使用9的话,整数部分会显示为空(即变成.xx),所以我们在整数部分最后一位用0,这样整数部分为0时就会显示0。 另外,格式模型中的FM可以去掉前导空格(以及前导0,但因为我们整数部分用了0,所以整数部分0会显示,而不会去掉)。如果不加FM,正数前面会有一个空格(用于放置负号,负数则显示负号)。所以通常我们加上FM。 因此,总结如下: 1. 基本转换(不带格式): SELECT TO_CHAR(123.45) FROM dual; -- 结果可能是 '123.45'(注意这里没有指定格式,所以小数部分末尾的0会被省略,比如123.0会变成'123') 2. 带格式转换(保留两位小数): SELECT TO_CHAR(123.45, 'FM9999999990.00') FROM dual; -- 结果为 '123.45' SELECT TO_CHAR(123, 'FM9999999990.00') FROM dual; -- 结果为 '123.00' SELECT TO_CHAR(0, 'FM9999999990.00') FROM dual; -- 结果为 '0.00' 3. 如果数字很大,整数部分需要很多位,可以增加9的个数,例如:'FM9999999999999999990.00'(18位整数)。 4. 如果希望有千分位分隔符,可以在格式模型中加上逗号,例如: SELECT TO_CHAR(1234567.89, 'FM9,999,999,990.00') FROM dual; -- 结果为 '1,234,567.89' 5. 如果数字是负数,使用上述格式,负数会显示负号,并且负号在数字前面。如果希望将负数用括号括起来,可以使用格式模型中的PR(表示负数用括号括起来,正数后面加空格): SELECT TO_CHAR(-123.45, 'FM9999999990.00PR') FROM dual; -- 结果为 '<123.45>' (注意:PR在正数时会在后面加一个空格,负数时加括号,所以一般和FM一起用,但FM不能去掉PR产生的括号,所以需要根据需求选择) 另外,引用[3]中虽然是将数字转换为日期字符串,但其中也使用了TO_CHAR,不过它是先将数字转换为日期(通过TO_DATE),然后再将日期转换为字符串。这和我们直接转换数字不同。 所以,针对用户的问题:将Oracle数据库中的NUMBER类型数据转换为字符串类型,我们推荐使用TO_CHAR函数,并根据需要指定格式。 如果用户没有特殊格式要求,可以直接使用TO_CHAR(number_value)进行转换。如果需要保留两位小数,则使用带格式的转换。 示例代码: ```sql -- 基本转换 SELECT TO_CHAR(123456) AS str FROM dual; -- 输出 '123456' SELECT TO_CHAR(123.456) AS str FROM dual; -- 输出 '123.456' SELECT TO_CHAR(123.0) AS str FROM dual; -- 输出 '123' -- 固定两位小数 SELECT TO_CHAR(123, 'FM9999999990.00') AS str FROM dual; -- 输出 '123.00' SELECT TO_CHAR(123.4, 'FM9999999990.00') AS str FROM dual; -- 输出 '123.40' SELECT TO_CHAR(123.456, 'FM9999999990.00') AS str FROM dual; -- 输出 '123.46'(四舍五入到两位小数) -- 注意:转换会四舍五入,如果不想四舍五入,需要先使用TRUNC函数截断小数位,再转换。 ``` 另外,如果数字很大,整数部分位数超过格式模型中指定的9的个数,那么转换结果会显示为多个#号(例如:'##########'),表示格式模型不足以显示该数字。因此,需要确保格式模型中的整数部分位数足够。 最后,关于引用[4]的内容,它讨论的是字符串转BLOB,与当前问题无关,故忽略。 因此,回答如下:</think>在 Oracle 数据库中,将 `NUMBER` 类型转换为字符串类型主要使用 `TO_CHAR()` 函数。以下是详细方法和示例: ### 1. **基本转换(不指定格式)** ```sql SELECT TO_CHAR(your_number_column) AS string_value FROM your_table; ``` - 直接转换,保留原始数值精度 - 示例:`TO_CHAR(123.45)` → `'123.45'` - 整数末尾的零会被省略:`TO_CHAR(100.00)` → `'100'`[^2] ### 2. **格式化转换(推荐)** ```sql SELECT TO_CHAR(your_number_column, 'FM9999999990.00') AS formatted_string FROM your_table; ``` - **格式说明**: - `FM`:移除前导空格(避免正数前的空格) - `9`:可选数字位(不显示前导零) - `0`:强制显示数字位(包括前导零) - `.00`:强制保留两位小数 - **示例**: - `TO_CHAR(0, 'FM9990.00')` → `'0.00'`[^2] - `TO_CHAR(2.1, 'FM9990.00')` → `'2.10'` - `TO_CHAR(12345.67, 'FM999,999,990.00')` → `'12,345.67'`(含千分位) ### 3. **处理特殊需求** #### (1) 固定整数位数(如 8 位) ```sql SELECT TO_CHAR(20240418, 'FM00000000') FROM dual; -- '20240418' ``` #### (2) 数字转日期字符串 ```sql SELECT TO_CHAR(TO_DATE('20240418', 'YYYYMMDD'), 'YYYY-MM-DD') FROM dual; -- '2024-04-18'[^3] ``` #### (3) 避免科学计数法 对大数字使用足够位数的格式模型: ```sql SELECT TO_CHAR(123456789012345, 'FM999999999999999') FROM dual; -- '123456789012345' ``` ### 4. **转换场景示例** #### 在查询中使用: ```sql SELECT product_id, TO_CHAR(price, 'FM$999,990.00') AS formatted_price FROM products; ``` 结果示例: ``` PRODUCT_ID FORMATTED_PRICE ---------- --------------- 1001 $199.99 1002 $15.50 ``` #### 在 INSERT 语句中使用: ```sql INSERT INTO reports (report_id, numeric_data, string_data) VALUES (101, 123.45, TO_CHAR(123.45, 'FM9990.00')); ``` ### 关键注意事项: 1. **小数处理** 未指定格式时,小数部分末尾的零会被自动省略[^2]。需要固定小数位时,必须显式声明格式。 2. **性能优化** 对频繁查询的列建立函数索引: ```sql CREATE INDEX idx_num_str ON your_table(TO_CHAR(numeric_column, 'FM9990.00')); ``` 3. **空值处理** `TO_CHAR(NULL)` 返回 `NULL`,建议用 `NVL` 处理: ```sql SELECT TO_CHAR(NVL(price, 0), 'FM990.00') FROM products; ``` --- ### 常见问题解答 **Q:为什么 `TO_CHAR(0)` 返回 `'0'` 但 `TO_CHAR(0.00)` 返回 `'0'`?** A:Oracle 会删除小数部分中无意义的零。如需显示固定小数位,必须指定格式模型[^2]。 **Q:如何避免转换后字符串前的空格?** A:使用 `FM` 格式修饰符(如 `'FM9990.00'`),它会移除格式化的前导空格。 **Q:数字转字符串会影响排序吗?** A:会!字符串 `'10'` 会排在 `'2'` 前面(按字符排序),建议在排序前保留数字类型。 --- ### 相关问题 1. 如何在 Oracle 中将字符串转换回数字类型2. 数字格式化中 `FM` 和 `FX` 修饰符有什么区别? 3. 如何处理转换时出现的 `ORA-01722: invalid number` 错误? 4. 在 WHERE 子句中使用 `TO_CHAR()` 会影响查询性能吗?如何优化? 5. Oracle 的 `CAST(number AS VARCHAR2)` 和 `TO_CHAR()` 有何区别?[^1][^2] > 引用来源: > [^1]: Oracle 数字转换文档 > [^2]: Oracle 数字格式化最佳实践 > [^3]: Oracle 日期转换技术指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值