Derby 对 NULL 值的处理

Posted

技术标签:

【中文标题】Derby 对 NULL 值的处理【英文标题】:Derby's handling of NULL values 【发布时间】:2011-02-05 08:11:34 【问题描述】:

我是 Derby 的新手,我注意到就null 值而言,我遇到了与使用 DB2 RDBMS 时类似的问题。 Derby 文档指出,null 值必须具有与之关联的类型(DB2 最终在 9.7 版本中删除了该类型):

http://db.apache.org/derby/docs/10.7/ref/crefsqlj21305.html

现在,我正在尝试在这里找到解决此问题的通用解决方案,因为这将成为我的数据库抽象库jOOQ 的一部分。下面的例子只是记录了这个问题。想想任何其他(更复杂的)例子。以下不起作用:

insert into T_AUTHOR (
  ID, FIRST_NAME, LAST_NAME, 
  DATE_OF_BIRTH, YEAR_OF_BIRTH, ADDRESS) 
select 
  1000, 'Lukas', 'Eder', 
  '1981-07-10', null, null 
from SYSIBM.SYSDUMMY1

也不这样做(这实际上是 jOOQ 所做的):

insert into T_AUTHOR (
  ID, FIRST_NAME, LAST_NAME, 
  DATE_OF_BIRTH, YEAR_OF_BIRTH, ADDRESS) 
select ?, ?, ?, ?, ?, ? 
from SYSIBM.SYSDUMMY1

因为这两个null 值没有与之关联的类型。解决方案是这样写:

insert into T_AUTHOR (
  ID, FIRST_NAME, LAST_NAME, 
  DATE_OF_BIRTH, YEAR_OF_BIRTH, ADDRESS) 
select 
  1000, 'Lukas', 'Eder', 
  '1981-07-10', cast(null as int), cast(null as varchar(500)) 
from SYSIBM.SYSDUMMY1

或者像这样,分别

insert into T_AUTHOR (
  ID, FIRST_NAME, LAST_NAME, 
  DATE_OF_BIRTH, YEAR_OF_BIRTH, ADDRESS) 
select 
  ?, ?, ?, ?, cast(? as int), cast(? as varchar(500)) 
from SYSIBM.SYSDUMMY1

但很多时候,在 Java 中,null 应该转换为的类型是未知的:

在此示例中,类型可以从插入子句派生,但对于更一般的用例来说,这可能被证明是复杂的或不可能的。 在其他示例中,我可以选择任何类型的转换(例如,始终转换为 int),但在此示例中不起作用,因为您不能将 cast(null as int) 值放入 ADDRESS。 使用 HSQLDB(此问题的另一个候选对象),我可以简单地编写 cast(null as object),这在大多数情况下都可以使用。但 Derby 没有 object 类型。

这个问题以前一直困扰着我使用 DB2,我还没有找到解决方案。有谁知道针对这些 RDBMS 中的任何一个的稳定通用解决方案?

德比 DB2

【问题讨论】:

【参考方案1】:

如果您在 INSERT 上使用 VALUES 子句,则不必强制转换 NULL 值:

insert into T_AUTHOR (
  ID, FIRST_NAME, LAST_NAME, 
  DATE_OF_BIRTH, YEAR_OF_BIRTH, ADDRESS) 
VALUES ( 
  1000, 'Lukas', 'Eder', 
  '1981-07-10', null, null 
);

这将像您期望的那样工作(即数据库可以确定 NULL 对应于一个整数和 varchar(500)。这在 DB2 和 Derby 中都有效(并且应该也适用于几乎任何其他数据库引擎) .

您也可以将 VALUES 与参数标记一起使用,而不必强制转换它们。

发出insert into ... select from 语句时必须强制转换的原因是因为SELECT 部分具有优先权——select 语句返回某些数据类型,无论它们是否与您尝试插入的表兼容进入。如果它们不兼容,您将得到一个错误(对于像 DB2

【讨论】:

谢谢伊恩。改写 SQL 不是一种选择,因为它只是一个描述性示例。我将不得不期待任何形式的 any 类型的 SQL。但是很好地暗示了SELECTINSERT .. SELECT 中的优先级。实际上,我的库中有许多可以顺利运行的集成测试,只是有些失败。我希望找到一个通用的解决方案来解决这个问题......但也许我会把它作为一个未解决的问题,希望 Derby 纠正它,因为 INSERT .. SELECT 语法不是很常见.【参考方案2】:

DB2 使用空指示符...例如

EXEC SQL INSERT INTO TABNAM (FILD1, FILD2) 
                     VALUES (:HOSTVAR1, :HOSTVAR2:NULL-IND2)  END-EXEC.

注意 NULL-IND2 字段可以设置为 -1 表示数据库表中的该字段应为空。

对于 JDBC 或 Derby 是否有类似的指标?

【讨论】:

我不知道有这样的指标,很高兴知道。我得分析一下。据我所知,JDCB 中没有这样的指标。特别是,命名参数在 JDBC 中也不可用。但是有一个选项可以在setNull() 方法中显式设置NULL 以及JDBC 类型。也不确定德比战【参考方案3】:

如果您将列值作为问号替换值保留在 PreparedStatement 中,然后在运行时使用 PreparedStatement.setObject(N, null) 设置这些值会怎样?

一般来说,您的库更喜欢通过替换参数值来提供列值,以处理字符串中的引号、数据类型转换等问题,我认为这种通用替换机制也应该处理您的空值问题.

【讨论】:

感谢布莱恩的输入。我不知道为什么我离开了绑定变量。当然,我的库将始终使用绑定变量来提高性能/安全性等,而不是将值直接内联到 SQL。我会调整这个问题。 不用担心。您可能遇到issues.apache.org/jira/browse/DERBY-1938,它最近才修复。您使用的是最新的 Derby (10.7) 吗?如果没有,试试看 setObject(null) 现在是否工作得更好。 是的,我使用的是 Derby 10.7.1.1。但实际上这是setObject(null) 的一个很好的提示。我有两个错误的组合,首先Connection.prepareStatement() 调用不接受null 文字或cast(? as integer) 表达式(如果整数是错误的类型)。当我简单地为空值准备? 时,我的库调用PreparedStatement.setNull(N, Object.class),它也不起作用。但是setObject(N, null) 似乎有效。我会仔细检查... 嗯,这在这种情况下有效,但不是通用解决方案。如果我只是SELECT ? FROM SYSIBM.SYSDUMMY1,那么德比会抱怨吗?在 select 子句中是不允许的。这确实是一个 SQL 方言抽象框架要解决的难题……【参考方案4】:

也许这个解决方案可以帮助你:

insert into T_AUTHOR (
    ID, 
    FIRST_NAME, 
    LAST_NAME, 
    DATE_OF_BIRTH, 
    YEAR_OF_BIRTH, 
    ADDRESS
) select 
    1000, 
    'Lukas', 
    'Eder', 
    '1981-07-10', 
    (select YEAR_OF_BIRTH from T_AUTHOR where true = false), 
    (select ADDRESS from T_AUTHOR where true = false) 
from SYSIBM.SYSDUMMY1

此方法不需要显式转换 null,因为内部 select 将返回具有所需类型的 null。

【讨论】:

以上是关于Derby 对 NULL 值的处理的主要内容,如果未能解决你的问题,请参考以下文章

Clickhouse 对null值的处理

Clickhouse 对null值的处理

数据库对null值的处理

MySQL分区表对NULL值的处理

FreeMarker 对null值的处理技巧

MYSQL NULL值怎么处理?