2

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

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

我认为这一定是我的编程语言的客户端库中的一个错误,所以我在 SQLite 命令行客户端中对其进行了测试——它仍然无法正常工作!

我对 SQLite 比较陌生,但多年来我一直在使用 Oracle、MS SQL Server 等,以前从未见过此类问题。

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

例子:

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

1|0|0|JPY|8|实收资本|C|X|0|X|0|0||||0|0|0|
0|0|0|JPY|11|根账户|P|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
3|0|0|JPY|13|三菱银行富通|A|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
4|0|0|JPY|14|日本邮政银行|A|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
...

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

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

在上面的 1 周围加上引号不会改变任何事情。

有趣的是,“select * from ledger where acctno > 1”返回结果!然而,由于它返回所有结果,它并不是非常有用。

我相信有人会问表结构,所以这里是:

sqlite> .schema 账本
创建表“分类帐”(
 “ACCTNO”整数(10,0)非空,
 “drbal”整数(20,0)非空,
 “crbal”整数(20,0)非空,
 "CURRKEY" char(3,0) NOT NULL,
 “TEXTKEY”整数(10,0),
 "文本" VARCHAR(64,0),
 "ACCTYPECD" CHAR(1,0) 非空,
 "ACCSTCD" CHAR(1,0),
 “PACCTNO”数字(10,0)不为空,
 “CATCD”编号(10,0),
 "TRANSNO" number(10,0) NOT NULL,
 “extrefno”数字(10,0),
 "更新用户" VARCHAR(32,0),
 “更新日期”文本(8,0),
 “更新时间”文本(6,0),
 “PAYEECD”数字(10,0)不为空,
 “drbal2”数字(10,0)不为空,
 “crbal2”数字(10,0)不为空,
 “delind”布尔值,
主键(“ACCTNO”),
约束 "fk_curr" 外键 ("CURRKEY") 引用 "CURRENCY" ("CUR
RKEY") ON DELETE RESTRICT ON UPDATE CASCADE
);

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

sqlite> select * from journalhdr where transno=13;
13|测试交易ATM取款20130213|20130223||20130223||

该表中的 TransNo 也是整数 (10,0) NOT NULL - 这就是让我觉得它与值有关的原因。

另一个线索是排序顺序似乎基于 ascii,而不是数字:

sqlite> select * from ledger order by acctno;
0|0|0|JPY|11|根账户|P|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|
1|0|0|JPY|8|实收资本|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|美元|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|日元|23|丸井|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|三菱银行富通|A|X|0|X|0|0|SYSTEM|20121209|150000|0|0|0|

当然,journalhdr 上的排序顺序(选择正常工作的地方)是数字的。

解决了!(有点) 数据可以这样固定:

sqlite>更新分类帐集acctno = 23 where rowid = 13; sqlite> select * from ledger where acctno = 25; 25|0|0|日元|0|测试|L|X|0|X|0|0|系统|20130224|132500|0|0|0|

尽管如此,如果它被存储为字符串,那么就会留下几个问题: 1. 为什么我不能使用引号将它选择为字符串?2. 它是一个有效的整数,它是如何存储为字符串的?3. 除了注意到奇怪的症状,你会如何正常检测这个问题?

虽然数据通常会由我的程序输入,但其中一些是使用 Navicat 手动创建的,所以我认为问题一定出在那里。

4

1 回答 1

0

你是 SQLite动态类型的受害者。

尽管 SQLite 定义了相似性类型的系统,它设置了一些关于如何将输入字符串或数字转换为实际内部值的规则,但它不会阻止使用准备好的语句的软件显式设置任何类型(和数据值)列(每行可能不同!)。

这可以通过这个简单的例子来说明:

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'

我插入的第二行不是整数,而是包含嵌入零字节的二进制字符串。这完全重现了您的问题,请逐步查看此SQLFiddle 。您也可以在 中执行这些命令sqlite3,您将得到相同的结果。

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

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

当然,很难从视觉上分辨"2\0"出 just "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 被强制为字符串或数字时,它们都会被 BLOB 中的尾随 0 弄糊涂。

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

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

长话短说,要么是你的一些客户端程序,要么是一些像 Navicat 这样的客户端工具把它搞砸了。

于 2013-02-25T08:39:55.040 回答