Skip to content

Commit af57c8b

Browse files
committed
docs: add NANVL documentation
1 parent 4c54d8e commit af57c8b

6 files changed

Lines changed: 602 additions & 0 deletions

File tree

‎CN/modules/ROOT/nav.adoc‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG 函数]
3333
** xref:master/oracle_compatibility/compat_alter_index_unusable.adoc[24、禁用索引]
3434
** xref:master/oracle_compatibility/compat_dbtimezone.adoc[25、dbtimezone]
35+
** xref:master/oracle_compatibility/nanvl.adoc[26、NANVL 函数]
3536
* 容器化与云服务
3637
** 容器化指南
3738
*** xref:master/containerization/k8s_deployment.adoc[K8S部署]
@@ -119,6 +120,7 @@
119120
**** xref:master/oracle_builtin_functions/stragg.adoc[stragg]
120121
**** xref:master/oracle_builtin_functions/dbtimezone_impl.adoc[dbtimezone]
121122
**** xref:master/oracle_builtin_functions/vsize.adoc[vsize]
123+
**** xref:master/oracle_builtin_functions/nanvl.adoc[nanvl]
122124
*** xref:master/gb18030.adoc[国标GB18030]
123125
* 参考指南
124126
** xref:master/tools_reference.adoc[工具参考]
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
= NANVL 函数设计
5+
6+
== 背景
7+
8+
=== Oracle 语义
9+
10+
Oracle 提供 `NANVL(n, m)` 函数,用于处理浮点数中的 NaN(Not a Number)值:
11+
12+
* 若 `n` 为 NaN,则返回替代值 `m`。
13+
* 若 `n` 不是 NaN,则返回 `n`。
14+
* 适用于 `BINARY_FLOAT` 和 `BINARY_DOUBLE` 类型。
15+
16+
=== 目的
17+
18+
NaN 是 IEEE 754 浮点标准定义的特殊值,在数据导入、科学计算和 ETL 场景中较为常见。若不处理 NaN,可能导致:
19+
20+
* 聚合运算结果变为 NaN
21+
* 比较运算行为异常(NaN ≠ NaN)
22+
* 索引和约束检查失效
23+
24+
`NANVL` 以声明方式将 NaN 替换为有效数值(如 0、-1 或均值),是将 Oracle 应用迁移到 IvorySQL 时需要的兼容函数。
25+
26+
== 架构设计
27+
28+
=== 设计选择
29+
30+
`NANVL` 使用 C 函数实现。该函数不需要特殊语法规则,可以作为普通函数调用由解析器识别。通过 `CREATE FUNCTION` 注册为普通 SQL 函数后,PostgreSQL 的标准函数查找机制可以自动解析重载。
31+
32+
=== 代码组织
33+
34+
[source,text]
35+
----
36+
contrib/ivorysql_ora/
37+
├── src/builtin_functions/
38+
│ ├── builtin_functions--1.0.sql -- SQL 注册
39+
│ └── numeric_datatype_functions.c -- C 函数实现
40+
├── sql/
41+
│ └── ora_nanvl.sql -- 回归测试 SQL
42+
├── expected/
43+
│ └── ora_nanvl.out -- 期望输出
44+
└── Makefile -- 在 ORA_REGRESS 列表中添加 ora_nanvl
45+
----
46+
47+
=== 函数属性
48+
49+
[cols="2,1,4",options="header"]
50+
|===
51+
|属性 |值 |原因
52+
|`IMMUTABLE` |是 |纯计算函数,相同输入始终返回相同输出
53+
|`PARALLEL SAFE` |是 |无副作用,可以安全地用于并行查询计划
54+
|===
55+
56+
== 实现细节
57+
58+
=== binary_float 和 binary_double 实现
59+
60+
[source,c]
61+
----
62+
Datum
63+
binary_float_nanvl(PG_FUNCTION_ARGS)
64+
{
65+
float4 arg1;
66+
67+
if (PG_ARGISNULL(0))
68+
PG_RETURN_NULL();
69+
70+
arg1 = PG_GETARG_FLOAT4(0);
71+
72+
if (!isnan(arg1))
73+
PG_RETURN_FLOAT4(arg1);
74+
75+
if (PG_ARGISNULL(1))
76+
PG_RETURN_NULL();
77+
78+
PG_RETURN_FLOAT4(PG_GETARG_FLOAT4(1));
79+
}
80+
----
81+
82+
* `BINARY_FLOAT` 底层为 `float4`(4 字节 IEEE 754 单精度),`BINARY_DOUBLE` 底层为 `float8`(8 字节双精度)。`binary_double_nanvl` 的结构相同,仅将 `float4` 和 `FLOAT4` 替换为 `float8` 和 `FLOAT8`。
83+
* 使用标准 C 库 `<math.h>` 中的 `isnan()` 检测 NaN。
84+
85+
=== number 类型实现
86+
87+
[source,c]
88+
----
89+
Datum
90+
number_nanvl(PG_FUNCTION_ARGS)
91+
{
92+
Numeric arg1;
93+
94+
if (PG_ARGISNULL(0))
95+
PG_RETURN_NULL();
96+
97+
arg1 = PG_GETARG_NUMERIC(0);
98+
99+
if (!numeric_is_nan(arg1))
100+
PG_RETURN_NUMERIC(arg1);
101+
102+
if (PG_ARGISNULL(1))
103+
PG_RETURN_NULL();
104+
105+
PG_RETURN_NUMERIC(PG_GETARG_NUMERIC(1));
106+
}
107+
----
108+
109+
* Oracle 的 `NUMBER` 类型映射到 PostgreSQL 的 `Numeric`。
110+
* PostgreSQL 的 `Numeric` 可以表示 NaN;Oracle 的 `NUMBER` 不支持 NaN。
111+
* 复用内核函数 `numeric_is_nan()`,该函数声明在 `utils/numeric.h` 中,定义在 `utils/adt/numeric.c` 中。
112+
* `numeric_is_nan()` 内部使用 `NUMERIC_IS_NAN()` 宏。
113+
114+
=== NULL 处理
115+
116+
`NANVL` 的语义仅依赖第一个参数 `n`:
117+
118+
. 若 `n` 为 NULL,直接返回 NULL,不读取 `m`。
119+
. 若 `n` 不是 NULL 且不是 NaN,直接返回 `n`。此时 `m` 不参与运算,即使 `m` 为 NULL 也不影响结果。
120+
. 只有 `n` 为 NaN 时才使用 `m` 作为返回值;此时若 `m` 为 NULL,结果才是 NULL。
121+
122+
因此,三个重载都不使用 `STRICT`,而是在函数体中使用 `PG_ARGISNULL()` 显式处理上述情况:先判断 `n` 是否为 NULL,再判断其是否为 NaN,仅在需要返回 `m` 时检查 `m` 是否为 NULL。`numeric`、`float4` 和 `float8` 三种类型的判空逻辑一致,仅 NaN 检测方式不同。
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
= NANVL 函数
5+
6+
== 概述
7+
8+
`NANVL` 是 Oracle 兼容函数,用于替换浮点数中的 NaN(Not a Number)值。当输入为 NaN 时返回指定的替代值,否则返回输入值本身。
9+
10+
== 语法
11+
12+
[source,sql]
13+
----
14+
NANVL(n, m)
15+
----
16+
17+
=== 参数
18+
19+
[cols="1,4",options="header"]
20+
|===
21+
|参数 |说明
22+
|`n` |待检测的数值表达式(`binary_float`、`binary_double`)
23+
|`m` |替代值,类型需与 `n` 兼容
24+
|===
25+
26+
=== 返回值
27+
28+
* 若 `n` 为 NaN,返回 `m`。
29+
* 若 `n` 不是 NaN,返回 `n`。
30+
31+
== 使用示例
32+
33+
=== 基本用法
34+
35+
[source,sql]
36+
----
37+
-- 正常值:返回原值
38+
SELECT NANVL(CAST(1.5 AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
39+
-- 结果:1.5
40+
41+
-- NaN 值:返回替代值
42+
SELECT NANVL(CAST('NaN' AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
43+
-- 结果:99
44+
45+
-- 负数正常值:返回原值
46+
SELECT NANVL(CAST(-3.14 AS BINARY_DOUBLE), CAST(0.0 AS BINARY_DOUBLE));
47+
-- 结果:-3.14
48+
----
49+
50+
=== 数据清洗
51+
52+
将表中的 NaN 值替换为 0:
53+
54+
[source,sql]
55+
----
56+
CREATE TABLE measurements (
57+
id int,
58+
temperature binary_double,
59+
pressure binary_double
60+
);
61+
62+
INSERT INTO measurements VALUES
63+
(1, 25.5, 1013.25),
64+
(2, 'NaN', 1015.0), -- 传感器故障,温度 NaN
65+
(3, 26.1, 'NaN'), -- 传感器故障,气压 NaN
66+
(4, 'NaN', 'NaN'); -- 两个传感器都故障
67+
68+
-- 将 NaN 替换为 0
69+
SELECT id,
70+
NANVL(temperature, 0.0) AS temp_clean,
71+
NANVL(pressure, 0.0) AS press_clean
72+
FROM measurements
73+
ORDER BY id;
74+
75+
-- 结果:
76+
-- id | temp_clean | press_clean
77+
-- ----+------------+-------------
78+
-- 1 | 25.5 | 1013.25
79+
-- 2 | 0 | 1015.0
80+
-- 3 | 26.1 | 0
81+
-- 4 | 0 | 0
82+
----
83+
84+
=== 使用表达式作为替代值
85+
86+
替代值可以是任意表达式:
87+
88+
[source,sql]
89+
----
90+
-- 用列的平均值替代 NaN
91+
SELECT NANVL(temperature,
92+
(SELECT AVG(temperature) FROM measurements))
93+
FROM measurements;
94+
95+
-- 用计算结果替代 NaN
96+
SELECT NANVL('NaN'::binary_float, 1.0 + 2.0::binary_float);
97+
-- 结果:3
98+
----
99+
100+
=== 配合聚合使用
101+
102+
[source,sql]
103+
----
104+
-- 计算温度时,先将 NaN 替换为 0 再求平均
105+
SELECT AVG(NANVL(temperature, 0.0)) AS avg_temp
106+
FROM measurements;
107+
----
108+
109+
=== 在 WHERE 子句中使用
110+
111+
[source,sql]
112+
----
113+
-- 筛选出“原始值为 NaN 或小于阈值”的行
114+
SELECT *
115+
FROM measurements
116+
WHERE NANVL(temperature, -999.0) < 0;
117+
----
118+
119+
== 边界行为
120+
121+
=== NULL 处理
122+
123+
`NANVL` 根据 `n` 决定返回值。当 `n` 不是 NaN 时直接返回 `n`,`m` 的值不影响结果。只有 `n` 为 NaN 时才会使用 `m`;此时若 `m` 为 NULL,则返回 NULL。
124+
125+
[source,sql]
126+
----
127+
SELECT NANVL(CAST(NULL AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
128+
-- n 为 NULL,直接返回 NULL
129+
130+
SELECT NANVL(CAST(1.5 AS BINARY_FLOAT), CAST(NULL AS BINARY_FLOAT));
131+
-- 返回 1.5;n 不是 NaN,m 是否为 NULL 不影响结果
132+
133+
SELECT NANVL(CAST(NULL AS BINARY_FLOAT), CAST(NULL AS BINARY_FLOAT));
134+
-- n 为 NULL,返回 NULL
135+
136+
SELECT NANVL(CAST('NaN' AS BINARY_FLOAT), CAST(NULL AS BINARY_FLOAT));
137+
-- n 是 NaN,返回 m;m 为 NULL,因此返回 NULL
138+
----
139+
140+
=== Infinity 不被替换
141+
142+
Infinity(无穷大)不是 NaN,不会被替换:
143+
144+
[source,sql]
145+
----
146+
SELECT NANVL(CAST('Infinity' AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
147+
-- 结果:Inf
148+
149+
SELECT NANVL(CAST('-Infinity' AS BINARY_DOUBLE), CAST(0.0 AS BINARY_DOUBLE));
150+
-- 结果:-Inf
151+
----
152+
153+
=== 负零不被替换
154+
155+
负零(`-0.0`)是有效的浮点数值,不是 NaN:
156+
157+
[source,sql]
158+
----
159+
SELECT NANVL(CAST(-0.0 AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
160+
-- 结果:0
161+
162+
SELECT NANVL(CAST(-0.0 AS NUMBER), CAST(99.0 AS NUMBER));
163+
-- 结果:0.0
164+
----
165+
166+
=== NaN 作为替代值
167+
168+
替代值本身也可以是 NaN:
169+
170+
[source,sql]
171+
----
172+
SELECT NANVL(
173+
CAST('NaN' AS BINARY_FLOAT),
174+
CAST('NaN' AS BINARY_FLOAT)
175+
);
176+
-- 结果:NaN
177+
----

‎EN/modules/ROOT/nav.adoc‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG function]
3333
** xref:master/oracle_compatibility/compat_alter_index_unusable_en.adoc[24、Alter Index Unusable]
3434
** xref:master/oracle_compatibility/compat_dbtimezone_en.adoc[24、dbtimezone]
35+
** xref:master/oracle_compatibility/nanvl.adoc[25、NANVL function]
3536
* Containerization and Cloud Service
3637
** Containerization
3738
*** xref:master/containerization/k8s_deployment.adoc[K8S deployment]
@@ -119,6 +120,7 @@
119120
*** xref:master/oracle_builtin_functions/stragg.adoc[stragg]
120121
*** xref:master/oracle_builtin_functions/dbtimezone_impl_en.adoc[dbtimezone]
121122
*** xref:master/oracle_builtin_functions/vsize_en.adoc[vsize]
123+
*** xref:master/oracle_builtin_functions/nanvl.adoc[nanvl]
122124
** xref:master/gb18030.adoc[GB18030 Character Set]
123125
* Reference
124126
** xref:master/tools_reference.adoc[Tool Reference]

0 commit comments

Comments
 (0)