问题描述
我有以下列:
...
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());
解决方法
参数 expr1
和 expr2
可以具有任何数据类型。如果它们的数据类型不同,则 Oracle 数据库会隐式地将一种转换为另一种。如果它们不能隐式转换,则数据库返回错误。隐式转换实现如下:
- 如果
expr1
是字符数据,则Oracle数据库在比较之前将expr2
转换为expr1
的数据类型,并返回{{1}字符集中的VARCHAR2
}}。 - 如果
expr1
是数字,则 Oracle 数据库确定哪个参数具有最高的数字优先级,将另一个参数隐式转换为该数据类型,并返回该数据类型。
Oracle 数据库使用短路评估。数据库评估每个 expr 值并确定它是否为 expr1
,而不是先评估所有 expr 值,然后再确定它们中的任何一个是否为 NULL
。
如果所有出现的 expr 都是数字数据类型或任何可以隐式转换为数字数据类型的非数字数据类型,则 Oracle 数据库确定具有最高数字优先级的参数,将其余参数隐式转换为该数据类型,并返回该数据类型。
您会注意到 NULL
明确声明它将执行隐式转换,因此 NVL
与 expr2
的数据类型相同,而 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));