为什么 NVL 在 Oracle 中通过 COALESCE 工作?

问题描述

我有以下列:

...
some_column NUMBER(1,0)  DEFAULT NULL NULL,...

用于保存可为空的 Integer 值。

现在我有一行填充了该列。我正在使用 Spring 的 JdbcTemplate 执行补丁,这意味着我只想在新值不为空时更新列:

UPDATE my_table SET some_column = COALESCE(?,some_column) WHERE...

这失败了:

Caused by: java.sql.sqlSyntaxErrorException: ORA-00932: inconsistent datatypes: expected CHAR got NUMBER

当我将 Java null 传递给 ? 时。这意味着 COALESCE 不高兴,因为我向它传递了两种不同的类型。我最初的假设是 Spring/JdbcTemplate 以某种方式不会将 sql null 传递给数据库,因为使用以下命令直接更新数据库

UPDATE my_table SET some_column = COALESCE(null,some_column) WHERE...

工作正常。但是,当我用以下内容替换查​​询时:

UPDATE my_table SET some_column = NVL(?,some_column) WHERE...

我用 JdbcTemplate 得到了我想要的。发生了什么,有什么不同?

更新:

我使用的Java代码如下:

我有

public class MyClass {
    private MyEnum enum;
    // Getters and setters
}

MyEnum

public enum MyEnum implements Serializable {
    SOME_VAL (0),SOME_OTHER_VAL (1),...

    private final int status;

    MyEnum (int status) {
        this.status = status;
    }

    public int getStatus() {
        return status;
    }

getJdbcTemplate().update("UPDATE my_table SET some_column = COALESCE(?,some_column) WHERE...",myClass.getMyEnum() == null ? null : myClass.getMyEnum().getStatus());

解决方法

来自NVL documentation

参数 expr1expr2 可以具有任何数据类型。如果它们的数据类型不同,则 Oracle 数据库会隐式地将一种转换为另一种。如果它们不能隐式转换,则数据库返回错误。隐式转换实现如下:

  • 如果expr1是字符数据,则Oracle数据库在比较之前将expr2转换为expr1的数据类型,并返回{{1}字符集中的VARCHAR2 }}。
  • 如果 expr1 是数字,则 Oracle 数据库确定哪个参数具有最高的数字优先级,将另一个参数隐式转换为该数据类型,并返回该数据类型。

来自COALESCE documentation

Oracle 数据库使用短路评估。数据库评估每个 expr 值并确定它是否为 expr1,而不是先评估所有 expr 值,然后再确定它们中的任何一个是否为 NULL

如果所有出现的 expr 都是数字数据类型或任何可以隐式转换为数字数据类型的非数字数据类型,则 Oracle 数据库确定具有最高数字优先级的参数,将其余参数隐式转换为该数据类型,并返回该数据类型。

您会注意到 NULL 明确声明它将执行隐式转换,因此 NVLexpr2 的数据类型相同,而 expr1(尽管措辞有点混乱)没有提到执行隐式转换(数字数据类型除外),并且期望其参数列表中的所有表达式都是相同的数据类型。

您对 COALESCE 的查询有效地转换为:

NVL

但是你的 UPDATE my_table SET some_column = CAST( NVL( :your_string_bind_variable,CAST( some_column AS VARCHAR2 ) ) AS NUMBER ) WHERE... 函数是:

COALESCE

UPDATE my_table SET some_column = COALESCE( :your_string_bind_variable,some_column ) WHERE... expr1 具有不同的数据类型,并且查询引发异常。


假设没有其他列被修改,如果值为 expr2,您不需要执行 UPDATE 因为它不会改变任何东西并且可以重写您的 Java 代码如:

NULL
,

重写该 UPDATE 语句明确,以便在 参数为 NULL 时根本不会更改该行。

从性能的角度来看,这比更新相同到相同要好得多(即使这样的no-update产生undo必须记录

这也将解决您与 coalesce 的问题(因为您没有使用它)

UPDATE my_table SET some_column =  ?  
WHERE ? is not NULL /* do NOT update if the new value is NULL */
and ...

您必须两次传递相同的值或使用命名绑定变量。

问题的根本原因是您将 null 参数作为 VARCHAR 数据类型传递。

如果您运行以下语句(注意显式转换),您可以验证它,您会得到观察到的错误

update tab
set some_column = COALESCE(cast (null as varchar2(10)),some_column) WHERE id = 1
;
-- SQL Error: ORA-00932: inconsistent datatypes: expected CHAR got NUMBER

所以最可靠的方法是明确地传递参数的数据类型。这是使用 SqlParameterValue

的示例
String sql = """update tab
set some_column = COALESCE(?,some_column) WHERE id = 1""";

// this fails
def updCnt = jdbcTemplate.update(sql,new SqlParameterValue(Types.VARCHAR,null));

// this works fine
def updCnt = jdbcTemplate.update(sql,new SqlParameterValue(Types.INTEGER,null));

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...