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
1 change: 1 addition & 0 deletions CN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[工具参考]
Expand Down
130 changes: 130 additions & 0 deletions CN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc
Original file line number Diff line number Diff line change
@@ -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 情况。
1 change: 1 addition & 0 deletions EN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
136 changes: 136 additions & 0 deletions EN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc
Original file line number Diff line number Diff line change
@@ -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.
Loading