Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG 函数]
** xref:master/oracle_compatibility/compat_alter_index_unusable.adoc[24、禁用索引]
** xref:master/oracle_compatibility/compat_dbtimezone.adoc[25、dbtimezone]
** xref:master/oracle_compatibility/nanvl.adoc[26、NANVL 函数]
* 容器化与云服务
** 容器化指南
*** xref:master/containerization/k8s_deployment.adoc[K8S部署]
Expand Down Expand Up @@ -119,6 +120,7 @@
**** xref:master/oracle_builtin_functions/stragg.adoc[stragg]
**** xref:master/oracle_builtin_functions/dbtimezone_impl.adoc[dbtimezone]
**** xref:master/oracle_builtin_functions/vsize.adoc[vsize]
**** xref:master/oracle_builtin_functions/nanvl.adoc[nanvl]
*** xref:master/gb18030.adoc[国标GB18030]
* 参考指南
** xref:master/tools_reference.adoc[工具参考]
Expand Down
122 changes: 122 additions & 0 deletions CN/modules/ROOT/pages/master/oracle_builtin_functions/nanvl.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
:sectnums:
:sectnumlevels: 5

= NANVL 函数设计

== 背景

=== Oracle 语义

Oracle 提供 `NANVL(n, m)` 函数,用于处理浮点数中的 NaN(Not a Number)值:

* 若 `n` 为 NaN,则返回替代值 `m`。
* 若 `n` 不是 NaN,则返回 `n`。
* 适用于 `BINARY_FLOAT` 和 `BINARY_DOUBLE` 类型。

=== 目的

NaN 是 IEEE 754 浮点标准定义的特殊值,在数据导入、科学计算和 ETL 场景中较为常见。若不处理 NaN,可能导致:

* 聚合运算结果变为 NaN
* 比较运算行为异常(NaN ≠ NaN)
* 索引和约束检查失效

`NANVL` 以声明方式将 NaN 替换为有效数值(如 0、-1 或均值),是将 Oracle 应用迁移到 IvorySQL 时需要的兼容函数。

== 架构设计

=== 设计选择

`NANVL` 使用 C 函数实现。该函数不需要特殊语法规则,可以作为普通函数调用由解析器识别。通过 `CREATE FUNCTION` 注册为普通 SQL 函数后,PostgreSQL 的标准函数查找机制可以自动解析重载。

=== 代码组织

[source,text]
----
contrib/ivorysql_ora/
├── src/builtin_functions/
│ ├── builtin_functions--1.0.sql -- SQL 注册
│ └── numeric_datatype_functions.c -- C 函数实现
├── sql/
│ └── ora_nanvl.sql -- 回归测试 SQL
├── expected/
│ └── ora_nanvl.out -- 期望输出
└── Makefile -- 在 ORA_REGRESS 列表中添加 ora_nanvl
----

=== 函数属性

[cols="2,1,4",options="header"]
|===
|属性 |值 |原因
|`IMMUTABLE` |是 |纯计算函数,相同输入始终返回相同输出
|`PARALLEL SAFE` |是 |无副作用,可以安全地用于并行查询计划
|===

== 实现细节

=== binary_float 和 binary_double 实现

[source,c]
----
Datum
binary_float_nanvl(PG_FUNCTION_ARGS)
{
float4 arg1;

if (PG_ARGISNULL(0))
PG_RETURN_NULL();

arg1 = PG_GETARG_FLOAT4(0);

if (!isnan(arg1))
PG_RETURN_FLOAT4(arg1);

if (PG_ARGISNULL(1))
PG_RETURN_NULL();

PG_RETURN_FLOAT4(PG_GETARG_FLOAT4(1));
}
----

* `BINARY_FLOAT` 底层为 `float4`(4 字节 IEEE 754 单精度),`BINARY_DOUBLE` 底层为 `float8`(8 字节双精度)。`binary_double_nanvl` 的结构相同,仅将 `float4` 和 `FLOAT4` 替换为 `float8` 和 `FLOAT8`。
* 使用标准 C 库 `<math.h>` 中的 `isnan()` 检测 NaN。

=== number 类型实现

[source,c]
----
Datum
number_nanvl(PG_FUNCTION_ARGS)
{
Numeric arg1;

if (PG_ARGISNULL(0))
PG_RETURN_NULL();

arg1 = PG_GETARG_NUMERIC(0);

if (!numeric_is_nan(arg1))
PG_RETURN_NUMERIC(arg1);

if (PG_ARGISNULL(1))
PG_RETURN_NULL();

PG_RETURN_NUMERIC(PG_GETARG_NUMERIC(1));
}
----

* Oracle 的 `NUMBER` 类型映射到 PostgreSQL 的 `Numeric`。
* PostgreSQL 的 `Numeric` 可以表示 NaN;Oracle 的 `NUMBER` 不支持 NaN。
* 复用内核函数 `numeric_is_nan()`,该函数声明在 `utils/numeric.h` 中,定义在 `utils/adt/numeric.c` 中。
* `numeric_is_nan()` 内部使用 `NUMERIC_IS_NAN()` 宏。

=== NULL 处理

`NANVL` 的语义仅依赖第一个参数 `n`:

. 若 `n` 为 NULL,直接返回 NULL,不读取 `m`。
. 若 `n` 不是 NULL 且不是 NaN,直接返回 `n`。此时 `m` 不参与运算,即使 `m` 为 NULL 也不影响结果。
. 只有 `n` 为 NaN 时才使用 `m` 作为返回值;此时若 `m` 为 NULL,结果才是 NULL。

因此,三个重载都不使用 `STRICT`,而是在函数体中使用 `PG_ARGISNULL()` 显式处理上述情况:先判断 `n` 是否为 NULL,再判断其是否为 NaN,仅在需要返回 `m` 时检查 `m` 是否为 NULL。`numeric`、`float4` 和 `float8` 三种类型的判空逻辑一致,仅 NaN 检测方式不同。
177 changes: 177 additions & 0 deletions CN/modules/ROOT/pages/master/oracle_compatibility/nanvl.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
:sectnums:
:sectnumlevels: 5

= NANVL 函数

== 概述

`NANVL` 是 Oracle 兼容函数,用于替换浮点数中的 NaN(Not a Number)值。当输入为 NaN 时返回指定的替代值,否则返回输入值本身。

== 语法

[source,sql]
----
NANVL(n, m)
----

=== 参数

[cols="1,4",options="header"]
|===
|参数 |说明
|`n` |待检测的数值表达式(`binary_float`、`binary_double`)
|`m` |替代值,类型需与 `n` 兼容
|===

=== 返回值

* 若 `n` 为 NaN,返回 `m`。
* 若 `n` 不是 NaN,返回 `n`。

== 使用示例

=== 基本用法

[source,sql]
----
-- 正常值:返回原值
SELECT NANVL(CAST(1.5 AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
-- 结果:1.5

-- NaN 值:返回替代值
SELECT NANVL(CAST('NaN' AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
-- 结果:99

-- 负数正常值:返回原值
SELECT NANVL(CAST(-3.14 AS BINARY_DOUBLE), CAST(0.0 AS BINARY_DOUBLE));
-- 结果:-3.14
----

=== 数据清洗

将表中的 NaN 值替换为 0:

[source,sql]
----
CREATE TABLE measurements (
id int,
temperature binary_double,
pressure binary_double
);

INSERT INTO measurements VALUES
(1, 25.5, 1013.25),
(2, 'NaN', 1015.0), -- 传感器故障,温度 NaN
(3, 26.1, 'NaN'), -- 传感器故障,气压 NaN
(4, 'NaN', 'NaN'); -- 两个传感器都故障

-- 将 NaN 替换为 0
SELECT id,
NANVL(temperature, 0.0) AS temp_clean,
NANVL(pressure, 0.0) AS press_clean
FROM measurements
ORDER BY id;

-- 结果:
-- id | temp_clean | press_clean
-- ----+------------+-------------
-- 1 | 25.5 | 1013.25
-- 2 | 0 | 1015.0
-- 3 | 26.1 | 0
-- 4 | 0 | 0
----

=== 使用表达式作为替代值

替代值可以是任意表达式:

[source,sql]
----
-- 用列的平均值替代 NaN
SELECT NANVL(temperature,
(SELECT AVG(temperature) FROM measurements))
FROM measurements;

-- 用计算结果替代 NaN
SELECT NANVL('NaN'::binary_float, 1.0 + 2.0::binary_float);
-- 结果:3
----

=== 配合聚合使用

[source,sql]
----
-- 计算温度时,先将 NaN 替换为 0 再求平均
SELECT AVG(NANVL(temperature, 0.0)) AS avg_temp
FROM measurements;
----

=== 在 WHERE 子句中使用

[source,sql]
----
-- 筛选出“原始值为 NaN 或小于阈值”的行
SELECT *
FROM measurements
WHERE NANVL(temperature, -999.0) < 0;
----

== 边界行为

=== NULL 处理

`NANVL` 根据 `n` 决定返回值。当 `n` 不是 NaN 时直接返回 `n`,`m` 的值不影响结果。只有 `n` 为 NaN 时才会使用 `m`;此时若 `m` 为 NULL,则返回 NULL。

[source,sql]
----
SELECT NANVL(CAST(NULL AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
-- n 为 NULL,直接返回 NULL

SELECT NANVL(CAST(1.5 AS BINARY_FLOAT), CAST(NULL AS BINARY_FLOAT));
-- 返回 1.5;n 不是 NaN,m 是否为 NULL 不影响结果

SELECT NANVL(CAST(NULL AS BINARY_FLOAT), CAST(NULL AS BINARY_FLOAT));
-- n 为 NULL,返回 NULL

SELECT NANVL(CAST('NaN' AS BINARY_FLOAT), CAST(NULL AS BINARY_FLOAT));
-- n 是 NaN,返回 m;m 为 NULL,因此返回 NULL
----

=== Infinity 不被替换

Infinity(无穷大)不是 NaN,不会被替换:

[source,sql]
----
SELECT NANVL(CAST('Infinity' AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
-- 结果:Inf

SELECT NANVL(CAST('-Infinity' AS BINARY_DOUBLE), CAST(0.0 AS BINARY_DOUBLE));
-- 结果:-Inf
----

=== 负零不被替换

负零(`-0.0`)是有效的浮点数值,不是 NaN:

[source,sql]
----
SELECT NANVL(CAST(-0.0 AS BINARY_FLOAT), CAST(99.0 AS BINARY_FLOAT));
-- 结果:0

SELECT NANVL(CAST(-0.0 AS NUMBER), CAST(99.0 AS NUMBER));
-- 结果:0.0
----

=== NaN 作为替代值

替代值本身也可以是 NaN:

[source,sql]
----
SELECT NANVL(
CAST('NaN' AS BINARY_FLOAT),
CAST('NaN' AS BINARY_FLOAT)
);
-- 结果:NaN
----
2 changes: 2 additions & 0 deletions EN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG function]
** xref:master/oracle_compatibility/compat_alter_index_unusable_en.adoc[24、Alter Index Unusable]
** xref:master/oracle_compatibility/compat_dbtimezone_en.adoc[24、dbtimezone]
** xref:master/oracle_compatibility/nanvl.adoc[25、NANVL function]
* Containerization and Cloud Service
** Containerization
*** xref:master/containerization/k8s_deployment.adoc[K8S deployment]
Expand Down Expand Up @@ -119,6 +120,7 @@
*** xref:master/oracle_builtin_functions/stragg.adoc[stragg]
*** xref:master/oracle_builtin_functions/dbtimezone_impl_en.adoc[dbtimezone]
*** xref:master/oracle_builtin_functions/vsize_en.adoc[vsize]
*** xref:master/oracle_builtin_functions/nanvl.adoc[nanvl]
** xref:master/gb18030.adoc[GB18030 Character Set]
* Reference
** xref:master/tools_reference.adoc[Tool Reference]
Expand Down
Loading
Loading