奇怪的SQLite行为:不在简单查询上返回结果

时间:2013-02-24 04:00:59

标签: sqlite

好的,所以我有一个名为“ledger”的基本表,它包含各种类型的字段,整数,varchar等。

在我的程序中,我曾经使用没有“from”谓词的查询来收集所有行,这当然可以正常工作。但是......我改变了我的代码,允许使用“where acctno = x”一次选择一行(其中X是我当时想要选择的帐号)。

我认为这对于我的编程语言来说必定是客户端库中的一个错误,所以我在SQLite命令行客户端中对它进行了测试 - 它仍然不起作用!

我对SQLite相对较新,但我多年来一直在使用Oracle,MS SQL Server等,之前从未见过这类问题。

我可以告诉你的其他事情: *使用其他整数字段的查询也不起作用 * char字段的查询工作 *将其作为字符串查询(使用引号上的帐号)仍然无法正常工作。 (我想也许这些数字无意中存储为一个字符串)。 *通过rowid访问行工作正常 - 这就是为什么我可以使用GUI工具编辑数据库而没有明显的问题。

示例:

没有WHERE的查询(工作正常):

1|0|0|JPY|8|Paid-In Capital|C|X|0|X|0|0||||0|0|0|
0|0|0|JPY|11|Root Account|P|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
3|0|0|JPY|13|Mitsubishi Bank Futsuu|A|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
4|0|0|JPY|14|Japan Post Bank|A|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
...

使用WHERE子句进行查询:(无结果)

sqlite> select * from ledger where acctno=1;                                    
sqlite> 

在上面的1上面加上引号不会改变任何内容。

有趣的是,“选择*来自分类帐,其中acctno> 1”返回结果!但是,因为它返回所有结果,所以它并不是很糟糕。

我确定有人会问表格结构,所以这里有:

sqlite> .schema ledger
CREATE TABLE "LEDGER" (
 "ACCTNO" integer(10,0) NOT NULL,
 "drbal" integer(20,0) NOT NULL,
 "crbal" integer(20,0) NOT NULL,
 "CURRKEY" char(3,0) NOT NULL,
 "TEXTKEY" integer(10,0),
 "TEXT" VARCHAR(64,0),
 "ACCTYPECD" CHAR(1,0) NOT NULL,
 "ACCSTCD" CHAR(1,0),
 "PACCTNO" number(10,0) NOT NULL,
 "CATCD" number(10,0),
 "TRANSNO" number(10,0) NOT NULL,
 "extrefno" number(10,0),
 "UPDATEUSER" VARCHAR(32,0),
 "UPDATEDATE" text(8,0),
 "UPDATETIME" TEXT(6,0),
 "PAYEECD" number(10,0) NOT NULL,
 "drbal2" number(10,0) NOT NULL,
 "crbal2" number(10,0) NOT NULL,
 "delind" boolean,
PRIMARY KEY("ACCTNO"),
CONSTRAINT "fk_curr" FOREIGN KEY ("CURRKEY") REFERENCES "CURRENCY" ("CUR
RKEY") ON DELETE RESTRICT ON UPDATE CASCADE
);

最奇怪的是,我有其他类似的表格可以正常工作!

sqlite> select * from journalhdr where transno=13;
13|Test transaction ATM Withdrawel 20130213|20130223||20130223||

TransNo in that table is also integer (10,0) NOT NULL - this is what makes me thing it is something to do with the values.

Another clue is that the sort order seems to be based on ascii, not numeric:

sqlite> select * from ledger order by acctno;
0|0|0|JPY|11|Root Account|P|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
1|0|0|JPY|8|Paid-In Capital|C|X|0|X|0|0||||0|0|0|
10|0|0|USD|20|Sallie Mae|L|X|0|X|0|0|SYSTEM|20121209|153900|0|0|0|
21|0|0|USD|21|Skrill|A|X|0|X|0|0|SYSTEM|20121209|154000|0|0|0|
22|0|0|USD|22|AES|L|X|0|X|0|0|SYSTEM|20121209|154200|0|0|0|
23|0|0|JPY|23|Marui|L|X|0|X|0|0|SYSTEM|20121209|154400|0|0|0|
24|0|0|JPY|24|Amex JP|L|X|0|X|0|0|SYSTEM|20121209|154500|0|0|0|
3|0|0|JPY|13|Mitsubishi Bank Futsuu|A|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|

Of course the sort order on journalhdr (where the select works properly) is numeric.

Solved! (sort-of) The data can be fixed like this:

sqlite> update ledger set acctno = 23 where rowid = 13; sqlite> select * from ledger where acctno = 25; 25|0|0|JPY|0|Test|L|X|0|X|0|0|SYSTEM|20130224|132500|0|0|0|

但是,如果它被存储为字符串,那么会留下一些问题: 1.为什么我不能使用引号将其选为字符串? 2.它是如何存储为字符串的,因为它是一个有效的整数? 3.除了注意到bizzarre症状之外,你会如何正常检测这个问题?

虽然我的程序通常会输入数据,但有些数据是使用Navicat手工创建的,所以我认为问题必须在那里。

1 个答案:

答案 0 :(得分:0)

您是SQLite dynamic typing的受害者。

即使SQLite定义了type affinity的系统,它设置了一些关于如何将输入字符串或数字转换为实际内部值的规则,但它并不阻止使用预准备语句的软件明确设置任何类型(列的数据值(并且每行可以不同!)。

这可以通过这个简单的例子来显示:

CREATE TABLE ledger (acctno INTEGER, name VARCHAR(16));
INSERT INTO ledger VALUES(1,          'John'); -- INTEGER '1'
INSERT INTO ledger VALUES(2 || X'00', 'Zack'); -- BLOB    '2\0'

我插入第二行不是INTEGER,而是插入包含嵌入式零字节的二进制字符串。这会准确地再现您的问题,请逐步查看此SQLFiddle。您也可以在sqlite3中执行这些命令,您将得到相同的结果。

以下是也会重现此问题的Perl脚本

此脚本仅创建两行acctno,其中第一行的值为1,第二行的值为"2\0""2\0"表示由2个字节组成的字符串:第一个是数字2,第二个是0(零)字节。

当然,仅从"2\0"直观地告诉"2"非常困难,但这是以下脚本演示的内容:

#!/usr/bin/perl -w
use strict;
use warnings;
use DBI qw(:sql_types);
my $dbh = DBI->connect("dbi:SQLite:test.db") or die DBI::errstr();
$dbh->do("DROP TABLE IF EXISTS ledger");
$dbh->do("CREATE TABLE ledger (acctno INTEGER, name VARCHAR(16))");
my $sth = $dbh->prepare(
    "INSERT INTO ledger (acctno, name) VALUES (?, ?)");
$sth->bind_param(1, "1", SQL_INTEGER);
$sth->bind_param(2, "John");
$sth->execute();
$sth->bind_param(1, "2\0", SQL_BLOB);
$sth->bind_param(2, "Zack");
$sth->execute();
$sth = $dbh->prepare(
    "SELECT count(*) FROM ledger WHERE acctno = ?");
$sth->bind_param(1, "1");
$sth->execute();
my ($num1) = $sth->fetchrow_array();
print "Number of rows matching id '1' is $num1\n";
$sth->bind_param(1, "2");
$sth->execute();
my ($num2) = $sth->fetchrow_array();
print "Number of rows matching id '2' is $num2\n";
$sth->bind_param(1, "2\0", SQL_BLOB);
$sth->execute();
my ($num3) = $sth->fetchrow_array();
print "Number of rows matching id '2<0>' is $num3\n";

此脚本的输出为:

Number of rows matching id '1' is 1
Number of rows matching id '2' is 0
Number of rows matching id '2<0>' is 1

如果您使用任何SQLite工具(包括sqlite3)查看结果表,它将为第二行打印2 - 当它被强制执行时,它们都会在BLOB中尾随0而感到困惑字符串或数字。

请注意,我必须使用自定义param绑定将类型强制转换为BLOB并允许存储空字节:

 $sth->bind_param(1, "2\0", SQL_BLOB);

长话短说,它可能是你的一些客户端程序,或者像Navicat这样的一些客户端工具搞砸了它。