破折号在 DBI 中造成 SQL 麻烦

Dashes Causing SQL Trouble in DBI

我有一个带有 WHERE 子句的 SQL 查询,该子句的值通常包括破折号,作为 CHAR(10) 存储在数据库中。当我像下面这样明确地调用它时:

$sth = $dbh->prepare("SELECT STATUS_CODE FROM MyTable WHERE ACC_TYPE = 'A-50C'");

它工作正常 returns 我的第 1 行;但是,如果我执行以下操作:

my $code = 'A-50C';
$sth = $dbh->prepare("SELECT STATUS_CODE FROM MyTable WHERE ACC_TYPE = ?");
$sth->execute($code);

或者我这样做:

my $code = 'A-50C';
$sth = $dbh->prepare("SELECT STATUS_CODE FROM MyTable WHERE ACC_TYPE = ?");
$sth->bind_param(1, $code);
$sth->execute();

查询完成,但我没有得到任何结果。我怀疑它与破折号被错误解释有关,但我不能 link 它是一个 Perl 问题,因为我已经使用 print "My Content: $code\n"; 打印了我的 $code 变量,所以我可以确认它不是st运行gely 转换。我还尝试为 bind_param 添加第三个值,如果我指定 ORA_VARCHAR2、SQL_VARCHAR 之类的值(尝试了所有可能性),我仍然得不到任何结果。如果我将其更改为长格式,即 { TYPE => SQL_VARCHAR } 它会给我一个错误

DBI::st=HASH<0x232a210>->bind_param(...): attribute parameter 'SQL_VARCHAR' is not a hash ref

最后,我尝试以不同的方式使用单引号和双引号以及反引号来转义值,但没有任何结果让我进入第 1 行,只有 0。有什么想法吗?尚未在文档或搜索中找到任何内容。这是oracle供参考。

带有错误检查的代码:

my $dbh = DBI->connect($dsn, $user, $pw, {PrintError => 0, RaiseError => 0})
  or die "$DBI::errstr\n";

# my $dbh = DBI->connect(); # connect

my $code = 'A-50C';
print "My Content: $code\n";
$sth = $dbh->prepare( "SELECT COUNT(*) FROM MyTable WHERE CODE = ?" )
  or die "Can't prepare SQL statement: $DBI::errstr\n";
$sth->bind_param(1, $code);
$sth->execute() or die "Can't execute SQL statement: $DBI::errstr\n";

my $outfile = 'output.txt';
open OUTFILE, '>', $outfile or die "Unable to open $outfile: $!";

while(my @re = $sth->fetchrow_array) {
    print OUTFILE @re,"\n";
}

warn "Data fetching terminated early by error: $DBI::errstr\n"
  if $DBI::err;

close OUTFILE;

$sth->finish();
$dbh->disconnect();

我运行查了一下回来:

-> bind_param for DBD::Oracle::st (DBI::st=HASH(0x22fbcc0)~0x3bcf48 2 'A-50C' HASH(0x22fbac8)) thr#3b66c8
dbd_bind_ph(1): bind :p2 <== 'A-50C' (type 0 (DEFAULT (varchar)), attribs: HASH(0x22fbac8))
dbd_rebind_ph_char() (1): bind :p2 <== 'A-50C' (size 5/16/0, ptype 4(VARCHAR), otype 1 )
dbd_rebind_ph_char() (2): bind :p2 <== ''A-50' (size 5/16, otype 1(VARCHAR), indp 0, at_exec 1)
    bind :p2 as ftype 1 (VARCHAR)
dbd_rebind_ph(): bind :p2 <== 'A-50C' (in, not-utf8, csid 178->0->178, ftype 1 (VARCHAR), csform 0(0)->0(0), maxlen 16, maxdata_size 0)

您的问题可能是将 CHARVARCHAR 数据放在一起比较的结果。

CHAR 数据类型臭名昭著(应该避免),因为它以固定长度格式存储数据。它永远不应该用于保存可变长度的数据。在您的情况下,存储在 ACC_TYPE 列中的数据将始终占用 10 个字符的存储空间。当您存储长度小于列大小的值时,如 A-50C,数据库将隐式填充字符串最多 10 个字符,因此存储的实际值变为 A-50C_____(其中 _代表一个空格)。

您的第一个查询有效,因为当您使用硬编码文字时,Oracle 会自动为您右填充该值 (A-50C -> A-50C_____)。但是,在您使用绑定变量的第二个查询中,您将 VARCHARCHAR 进行比较,并且不会发生自动填充。

作为问题的快速修复,您可以向查询添加右填充:

SELECT STATUS_CODE FROM MyTable WHERE ACC_TYPE = rpad(?, 10)

一个长期的解决方案是避免在 table 定义中使用 CHAR 数据类型,而改用 VARCHAR2

如您的 DBI_TRACE 所示,ACC_TYPE 列是 CHAR(10),但绑定参数被理解为 VARCHAR。

当相互比较 CHAR、NCHAR 或文字字符串时,实际上会忽略尾随空白。 (请记住,CHAR(10) 意味着 ACC_TYPE 值被填充为 10 个字符长。)因此,作为 CHAR,'A ''A' 比较相等。然而,当与 VARCHAR 系列进行比较时,尾随空白变得很重要,并且如果一个是 VARCHAR 变体,'A ' 不再等于“A”。

您可以在 sqlplus 或通过快速 DBI 查询确认:

SELECT COUNT(1) FROM DUAL WHERE CAST('A' AS CHAR(2)) = 'A'; -- or CAST AS CHAR(whatever)
SELECT COUNT(1) FROM DUAL WHERE CAST('A' AS CHAR(2)) = CAST('A' AS VARCHAR(1));

(Oracle 术语这些 blank-padded and nonpadded comparison semantics,因为根据 ANSI-92 规范的实际行为是将较短的 CHAR 或文字填充到较长的长度,然后进行比较。有效的行为,无论名称如何, 一个忽略尾随空格而另一个不忽略。)

一样,RPAD()ing 绑定值将起作用,或者更好的是,将列类型更改为 VARCHAR。你也可以 CAST(? AS CHAR(10))。 TRIM(TRAILING FROM ACC_TYPE) 也可以,但要忽略该列上的任何索引。