为什么 NVL 在 Oracle 中优于 COALESCE?
Why does NVL work over COALESCE in Oracle?
我有以下列:
...
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());
The arguments expr1
and expr2
can have any data type. If their data types are different, then Oracle Database implicitly converts one to the other. If they cannot be converted implicitly, then the database returns an error. The implicit conversion is implemented as follows:
- If
expr1
is character data, then Oracle Database converts expr2
to the data type of expr1
before comparing them and returns VARCHAR2
in the character set of expr1
.
- If
expr1
is numeric, then Oracle Database determines which argument has the highest numeric precedence, implicitly converts the other argument to that data type, and returns that data type.
Oracle Database uses short-circuit evaluation. The database evaluates each expr value and determines whether it is NULL
, rather than evaluating all of the expr values before determining whether any of them is NULL
.
If all occurrences of expr are numeric data type or any non-numeric data type that can be implicitly converted to a numeric data type, then Oracle Database determines the argument with the highest numeric precedence, implicitly converts the remaining arguments to that data type, and returns that data type.
您会注意到 NVL
明确声明它将执行隐式转换,因此 expr2
是与 expr1
相同的数据类型,而 COALESCE
(虽然有点令人困惑worded) 没有提到执行隐式转换(数字数据类型除外)并且期望其参数列表中的所有表达式都是相同的数据类型。
您对 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
如果值是 NULL
因为它不会改变任何东西并且可以重写你的Java 代码为:
MyEnum enumValue = myClass.getMyEnum();
if ( enumValue != null )
{
getJdbcTemplate().update(
"UPDATE my_table SET some_column = ? WHERE...",
enumValue.getStatus()
);
}
重写 UPDATE
语句 显式 以便在 参数为 NULL 时根本不更改该行。
从性能的角度来看,这比 将相同更新为相同 要好得多(即使这样 no-update 也会产生 undo 必须 logged.
这也将解决您使用 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));
我有以下列:
...
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());
The arguments
expr1
andexpr2
can have any data type. If their data types are different, then Oracle Database implicitly converts one to the other. If they cannot be converted implicitly, then the database returns an error. The implicit conversion is implemented as follows:
- If
expr1
is character data, then Oracle Database convertsexpr2
to the data type ofexpr1
before comparing them and returnsVARCHAR2
in the character set ofexpr1
.- If
expr1
is numeric, then Oracle Database determines which argument has the highest numeric precedence, implicitly converts the other argument to that data type, and returns that data type.
Oracle Database uses short-circuit evaluation. The database evaluates each expr value and determines whether it is
NULL
, rather than evaluating all of the expr values before determining whether any of them isNULL
.If all occurrences of expr are numeric data type or any non-numeric data type that can be implicitly converted to a numeric data type, then Oracle Database determines the argument with the highest numeric precedence, implicitly converts the remaining arguments to that data type, and returns that data type.
您会注意到 NVL
明确声明它将执行隐式转换,因此 expr2
是与 expr1
相同的数据类型,而 COALESCE
(虽然有点令人困惑worded) 没有提到执行隐式转换(数字数据类型除外)并且期望其参数列表中的所有表达式都是相同的数据类型。
您对 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
如果值是 NULL
因为它不会改变任何东西并且可以重写你的Java 代码为:
MyEnum enumValue = myClass.getMyEnum();
if ( enumValue != null )
{
getJdbcTemplate().update(
"UPDATE my_table SET some_column = ? WHERE...",
enumValue.getStatus()
);
}
重写 UPDATE
语句 显式 以便在 参数为 NULL 时根本不更改该行。
从性能的角度来看,这比 将相同更新为相同 要好得多(即使这样 no-update 也会产生 undo 必须 logged.
这也将解决您使用 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));