Firebird POSITION出乎意料的行为?

时间:2012-11-08 19:01:22

标签: linq-to-entities firebird

我在Firebird 2.5.2中有一张表:

create table SearchTest ( val varchar(20) )

有两行:

insert into SearchTest ( val ) values ('one')
insert into SearchTest ( val ) values ('three')

我想选择列'val'包含'one'或'hre'的所有行。使用linq我可以表达为:

var a = from b in TestEntities.SEARCHTESTs
        from c in new []{ "one", "hre" }
        where b.VAL.Contains(c)
        select b;

这会生成如下查询:

SELECT
"C"."VAL" AS "VAL"
FROM  "SEARCHTEST" AS "C"
CROSS JOIN  (SELECT
        _UTF8 X'4F4E45' AS "C1"
        FROM  ( SELECT 1 AS X FROM RDB$DATABASE) AS "D"
UNION ALL
        SELECT
        _UTF8 X'485245' AS "C1"
        FROM  ( SELECT 1 AS X FROM RDB$DATABASE) AS "E") AS "F"
WHERE ((POSITION("F"."C1", "C"."VAL")) > 0)

为便于检查,这也是做同样的事情:

SELECT
  val,
  substr,
  POSITION(Substr, Val) as pos
FROM
  SearchTest
CROSS JOIN
(
  SELECT 'one' AS substr FROM RDB$DATABASE
  UNION ALL
  SELECT 'hre' AS substr FROM RDB$DATABASE
)

使用搜索词“one”和“hre”,结果如您所愿:

val   substr pos
---   ------ ---
one   one    1
three one    0
one   hre    0
three hre    2

但是,如果搜索词的长度不匹配:

SELECT
  val,
  substr,
  POSITION(Substr, Val) as pos
FROM
  SearchTest
CROSS JOIN
(
  SELECT 'one' AS substr FROM RDB$DATABASE
  UNION ALL
  SELECT 'hree' AS substr FROM RDB$DATABASE
)

比赛失败:

val   substr pos
---   ------ ---
one   one    1
three one    0
one   hree   0
three hree   0

如果我投射搜索词(投射类型不必匹配,如此处所示):

SELECT
  val,
  substr,
  POSITION(Substr, Val) as pos
FROM
  SearchTest
CROSS JOIN
(
  SELECT cast('one' as varchar(3)) AS substr FROM RDB$DATABASE
  UNION ALL
  SELECT cast('hree' as char(5)) AS substr FROM RDB$DATABASE
)

比赛再次起作用:

val   substr pos
---   ------ ---
one   one    1
three one    0
one   hree   0
three hree   2

为什么会这样,并且有办法解决它吗?

修改

Jiri Cincura已经注意到此错误已在下一版本中修复;字符串常量现在显式转换为varchars。 Firebird跟踪器问题:http://tracker.firebirdsql.org/browse/DNET-466

1 个答案:

答案 0 :(得分:3)

Firebird将文字视为CHAR,因此当您在此处有两个不同长度的文字('one''hree')时,它会将它们描述为CHAR(4)

为了说明这一点,带有SQLDA_DISPLAY ON的ISQL中的输出用于类似的查询:

SQL> SET SQLDA_DISPLAY ON;
SQL> SELECT 'one' as X FROM RDB$DATABASE
CON> UNION ALL
CON> SELECT 'hree' as x FROM RDB$DATABASE;

INPUT  SQLDA version: 1 sqln: 10 sqld: 0

OUTPUT SQLDA version: 1 sqln: 20 sqld: 1
01: sqltype: 452 TEXT                    sqlscale: 0 sqlsubtype: 0 sqllen: 4
  :  name: (0)  alias: (1)X
  : table: (0)  owner: (0)

X
======
one
hree

Type 452 TEXT是CHAR列的Firebird类型。 sqllen表示长度为4. 'one'这意味着它实际上是'one '(请注意额外的空格)。因此当它被馈送到POSITION时,它不匹配,因为你的原始值是'one'(作为VARCHAR)。

我不确定它是POSITION中的错误(CHAR的行为是SQL标准的烦人特征),还是Firebird使用CHAR文字的方式需要更改为VARCHAR。确实令人困惑,因此我建议将其报告为http://tracker.firebirdsql.org/browse/CORE

上的错误

现在,如果您正在编写直接查询,则解决方案与上一个示例相同。由于您将其中一个文字转换为VARCHAR,因此联合会自动将所有值转换为VARCHAR

SQL> SELECT CAST('one' AS VARCHAR(3)) as X FROM RDB$DATABASE
CON> UNION ALL
CON> SELECT 'hree' as x FROM RDB$DATABASE;

INPUT  SQLDA version: 1 sqln: 10 sqld: 0

OUTPUT SQLDA version: 1 sqln: 20 sqld: 1
01: sqltype: 448 VARYING                 sqlscale: 0 sqlsubtype: 21 sqllen: 4
  :  name: (0)  alias: (1)X
  : table: (0)  owner: (0)

X
======
one
hree

类型448 VARYING是VARCHAR的Firebird类型。请注意,长度为4而不是3,因为Firebird会将其扩展到找到的最大大小(在UNION的第二部分中)。

由于您没有直接编写SQL,我不确定这里的解决方案是什么。您可以为Firebird .NET提供程序添加一个改进请求,以便为这些类型的转换将值转换为VARCHAR(在http://tracker.firebirdsql.org/browse/DNET的跟踪器中)