diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index 7ed31aff..8a94e0a5 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -119,6 +119,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/lnnvl.adoc[lnnvl] *** xref:master/gb18030.adoc[国标GB18030] * 参考指南 ** xref:master/tools_reference.adoc[工具参考] diff --git a/CN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc b/CN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc new file mode 100644 index 00000000..eb9f6d7b --- /dev/null +++ b/CN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc @@ -0,0 +1,130 @@ +:sectnums: +:sectnumlevels: 5 + += LNNVL + +== 功能概述 + +IvorySQL 提供兼容 Oracle 的内置函数 `LNNVL(condition)`。当条件结果为 `TRUE` 时返回 +`FALSE`;当条件结果为 `FALSE` 或 `UNKNOWN`(NULL)时返回 `TRUE`。 + +该行为等价于 SQL 谓词 `condition IS NOT TRUE`。与 `NOT condition` 不同:条件为 NULL +时,`NOT` 的结果为 `UNKNOWN`,在 `WHERE` 子句中会丢失这些行;而 `LNNVL` 将未知条件 +视为满足条件。因此,`LNNVL` 适合用于筛选“不满足条件”的数据,同时保留条件结果为 +NULL 的行。 + +== 语法 + +``` +LNNVL(condition) +``` + +[cols="2,6"] +|==== +|*参数* |*说明* +|condition |需要按 Oracle 兼容的空值语义取反的布尔表达式。 +|==== + +返回类型:`boolean`。 + +== 真值表 + +[cols="2,2,3"] +|==== +|*条件结果* |*LNNVL 结果* |*WHERE 子句是否返回该行* +|TRUE |FALSE |否 +|FALSE |TRUE |是 +|UNKNOWN |TRUE |是 +|==== + +`LNNVL(NULL)` 同样返回 `TRUE`,因为 `NULL IS NOT TRUE` 的结果为 `TRUE`。 + +== 示例 + +下面的查询展示了条件结果为 TRUE、FALSE 和 UNKNOWN 时的返回值: + +``` +SELECT LNNVL(1 = 1) AS true_condition, + LNNVL(1 = 2) AS false_condition, + LNNVL(NULL::boolean) AS unknown_condition; +``` + +``` + true_condition | false_condition | unknown_condition +----------------+-----------------+------------------- + f | t | t +(1 row) +``` + +假设需要查询所有不满足 `amount >= 60` 的数据。普通 `NOT` 会丢弃 `amount` 为 NULL +的行,而 `LNNVL` 会保留这些行: + +``` +CREATE TEMP TABLE lnnvl_orders (id int, amount numeric); +INSERT INTO lnnvl_orders VALUES (1, 100), (2, NULL), (3, 50); + +SELECT id FROM lnnvl_orders +WHERE NOT (amount >= 60) +ORDER BY id; + + id +---- + 3 +(1 row) + +SELECT id FROM lnnvl_orders +WHERE LNNVL(amount >= 60) +ORDER BY id; + + id +---- + 2 + 3 +(2 rows) + +DROP TABLE lnnvl_orders; +``` + +`LNNVL` 也可用于 `LIKE`、`BETWEEN`、`IN`、`EXISTS` 等条件: + +``` +SELECT LNNVL('ab' LIKE 'a%') AS like_result, + LNNVL(1 BETWEEN 0 AND 5) AS between_result, + LNNVL(EXISTS (SELECT 1 FROM dual)) AS exists_result; +``` + +``` + like_result | between_result | exists_result +-------------+----------------+--------------- + f | f | f +(1 row) +``` + +== 兼容性说明 + +* 参数必须是布尔表达式。`LNNVL(1)` 会报错,因为该函数没有整数重载,也不支持从整数 + 到布尔值的隐式转换。 +* 在 Oracle 兼容模式下,可以直接调用 `LNNVL(condition)`;在 PostgreSQL 模式下, + 需要使用带模式限定的形式 `sys.lnnvl(condition)`。 +* IvorySQL 接受任意布尔表达式作为参数,而 Oracle 只接受单个简单条件。若 SQL 还需要 + 在 Oracle 上运行,应将复合条件拆分为多个 `LNNVL` 调用,并使用 `AND` 或 `OR` 组合: + +``` +SELECT LNNVL(a > 1) OR LNNVL(b < 2); +``` + +== 实现原理 + +函数在 `contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql` 中注册: + +```sql +CREATE FUNCTION sys.lnnvl(pg_catalog.bool) +RETURNS pg_catalog.bool +AS $$SELECT $1 IS NOT TRUE$$ +LANGUAGE sql +CALLED ON NULL INPUT +PARALLEL SAFE +IMMUTABLE; +``` + +直接使用 `IS NOT TRUE` 即可得到与 Oracle 一致的真值表,包括 NULL 情况。 diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index 594bda81..33dbf48a 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -119,6 +119,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/lnnvl.adoc[lnnvl] ** xref:master/gb18030.adoc[GB18030 Character Set] * Reference ** xref:master/tools_reference.adoc[Tool Reference] diff --git a/EN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc b/EN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc new file mode 100644 index 00000000..c884b9d2 --- /dev/null +++ b/EN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc @@ -0,0 +1,136 @@ +:sectnums: +:sectnumlevels: 5 + += LNNVL + +== Overview + +IvorySQL provides the Oracle-compatible built-in function `LNNVL(condition)`. +It returns `FALSE` when the condition evaluates to `TRUE`, and returns `TRUE` +when the condition evaluates to `FALSE` or `UNKNOWN` (NULL). + +This behavior is equivalent to the SQL predicate `condition IS NOT TRUE`. +Unlike `NOT condition`, which evaluates to `UNKNOWN` when the condition is +NULL, `LNNVL` treats an unknown condition as a matching condition. This makes +it useful in `WHERE` clauses when rows that do not satisfy a condition must be +returned without losing the rows whose condition is NULL. + +== Syntax + +``` +LNNVL(condition) +``` + +[cols="2,6"] +|==== +|*Parameter* |*Description* +|condition |A boolean expression to negate with Oracle-compatible NULL semantics. +|==== + +Return type: `boolean`. + +== Truth table + +[cols="2,2,3"] +|==== +|*Condition result* |*LNNVL result* |*Returned by a WHERE clause* +|TRUE |FALSE |No +|FALSE |TRUE |Yes +|UNKNOWN |TRUE |Yes +|==== + +`LNNVL(NULL)` also returns `TRUE`, because `NULL IS NOT TRUE` evaluates to +`TRUE`. + +== Examples + +The following query shows the three possible condition results: + +``` +SELECT LNNVL(1 = 1) AS true_condition, + LNNVL(1 = 2) AS false_condition, + LNNVL(NULL::boolean) AS unknown_condition; +``` + +``` + true_condition | false_condition | unknown_condition +----------------+-----------------+------------------- + f | t | t +(1 row) +``` + +Suppose a query must return all rows that do not satisfy `amount >= 60`. +A plain `NOT` drops rows where `amount` is NULL, while `LNNVL` keeps them: + +``` +CREATE TEMP TABLE lnnvl_orders (id int, amount numeric); +INSERT INTO lnnvl_orders VALUES (1, 100), (2, NULL), (3, 50); + +SELECT id FROM lnnvl_orders +WHERE NOT (amount >= 60) +ORDER BY id; + + id +---- + 3 +(1 row) + +SELECT id FROM lnnvl_orders +WHERE LNNVL(amount >= 60) +ORDER BY id; + + id +---- + 2 + 3 +(2 rows) + +DROP TABLE lnnvl_orders; +``` + +`LNNVL` can be used with conditions such as `LIKE`, `BETWEEN`, `IN`, and +`EXISTS`: + +``` +SELECT LNNVL('ab' LIKE 'a%') AS like_result, + LNNVL(1 BETWEEN 0 AND 5) AS between_result, + LNNVL(EXISTS (SELECT 1 FROM dual)) AS exists_result; +``` + +``` + like_result | between_result | exists_result +-------------+----------------+--------------- + f | f | f +(1 row) +``` + +== Compatibility notes + +* The condition must be a boolean expression. `LNNVL(1)` raises an error + because there is no integer overload or implicit cast from an integer. +* In Oracle-compatible mode, call the function as `LNNVL(condition)`. + In PostgreSQL mode, use the schema-qualified form `sys.lnnvl(condition)`. +* IvorySQL accepts any boolean expression as the argument. Oracle accepts a + single simple condition. For SQL that must also run on Oracle, write a + compound condition as separate `LNNVL` calls combined with `AND` or `OR`: + +``` +SELECT LNNVL(a > 1) OR LNNVL(b < 2); +``` + +== Implementation + +The function is registered in `contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql`: + +```sql +CREATE FUNCTION sys.lnnvl(pg_catalog.bool) +RETURNS pg_catalog.bool +AS $$SELECT $1 IS NOT TRUE$$ +LANGUAGE sql +CALLED ON NULL INPUT +PARALLEL SAFE +IMMUTABLE; +``` + +Using `IS NOT TRUE` directly gives the Oracle-compatible truth table, including +the NULL case.