【问题标题】:How to count many things with SQLite under Perl?如何在 Perl 下用 SQLite 计算很多东西?
【发布时间】:2010-01-20 06:51:05
【问题描述】:

我想统计很多字符串(>3G),所以我选择了带有(str TEXT PRIMARY KEY, count INTEGER DEFAULT 1)的表的SQLite。

大约有3G的字符串,每个占40*2/8=10字节,所以整个字符串是30GB。 这10个字节中有2^80种,比3G大很多。

那么如何有效更新呢?

UPDATE table SET count = count + 1 WHERE str = 'xxx';
# check whether rows infected
INSERT INTO table (str) VALUES ('yyy')

或者某事。比如INSERT OR REPLACE,我不太熟悉。

有什么建议吗?


我遵循思南乌努尔的方式:

PRAGMA synchronous = OFF;
PRAGMA journal_mode = OFF;
PRAGMA temp_store = MEMORY;
PRAGMA auto_vacuum = NONE;
PRAGMA cache_size = 4000000;
CREATE TABLE kmers ( seq TEXT );

SELECT seq,COUNT(seq) FROM kmers GROUP BY seq;

没有使用索引。自动提交为 0。

我还没有测试journal_mode OFF 是否更快。 temp_store 应该没用。

【问题讨论】:

  • 您使用数据库是否有特殊原因?您是否希望只有很少的重复项并且担心内存?我不明白为什么你觉得你不能在内存中做到这一点。
  • 这和 Perl 有关系吗?我倾向于删除Perl 标签,但我会稍等片刻,看看你能否澄清一下。
  • 你能谈谈你想用这个完成什么吗?只计算你看到一个字符串的次数?

标签: sql sqlite


【解决方案1】:

这真的不是 Perl 问题,而是 SQL 问题。在任何情况下,您都不需要 COUNT 列,因为 SQLite 提供了一个内置的 count 函数来为您进行计数:

SELECT str, countr(str) FROM mytable GROUP BY str

应该给你每个唯一的str 以及它在表格中出现的次数。

当然,如果你定义你的表以str为主键,你不能根据定义插入多个strs,所以你的表结构需要待完善。

更新:

如果我要这样做(我不确定我是否会这样做),我会设置一个表,其中包含一个自动生成的 id 列和一个字符串列。 SQLite 的INTEGER PRIMARY KEY,一个 64 位整数足以为每个插入的字符串分配一个唯一的 id。

然后,我将使用上面的查询按字符串获取频率。

如果您通过 Perl 的 DBI 插入,请确保在插入期间关闭 AutoCommit 并记住在最后(或定期)提交。

创建索引似乎几乎是强制性的,但它应该在所有字符串都在数据库中之后并且在运行任何查询之前完成

#!/usr/bin/perl

use strict; use warnings;

use DBI;

my $dbh = DBI->connect('dbi:SQLite:counter.db', undef, undef,
    { RaiseError => 1, AutoCommit => 0 },
);

my $sth = $dbh->prepare(q{
    INSERT INTO strings (string) VALUES(?)
});

my @strings = qw( 0123456789 9876543210 );

for ( 1 .. 10 ) {
    my $val = $strings[0.5 > rand];
    $sth->execute($val);
}

$dbh->commit;

my $result = $dbh->selectall_hashref(
    q{SELECT string, count(string) FROM strings GROUP BY string},
    'string',
);

$dbh->disconnect;

use Data::Dumper;
print Dumper $result;

SQL:

DROP TABLE strings;

CREATE TABLE strings (
    id INTEGER PRIMARY KEY,
    string char(10)
);

输出:

$VAR1 = { '9876543210' => { '计数(字符串)' => '9', '字符串' => '9876543210' }, '0123456789' => { '计数(字符串)' => '1', '字符串' => '0123456789' } };

【讨论】:

  • 我觉得字符串不在数据库中。他计划将它们添加到数据库中作为计数的一种方式。在这种情况下,您的建议变为:插入重复项,然后让 SQL 计算它们。
  • 那么insert duplicates, then let SQL count them 会比insert first then update 快吗?
  • @Galaxy:我们不知道。你得试试看。这可能很大程度上取决于有多少重复。
  • 如果我尝试insert duplicates, then count,我应该什么时候在str 上添加索引?计数的直方图应该是泊松分布。
  • @cjm:是的,完全正确,插入重复项并让 SQL 计算它们。将所有字符串放入数据库的好处是以后可以针对它们运行其他查询,而无需再次执行导入过程。
【解决方案2】:

INSERT OR REPLACE 大致相当于在执行INSERT 之前使用要插入的行中的值对唯一约束执行DELETE。它对您的目的没有用,因为您无法从旧行中获取计数器的值。 (新行的值在确定是否存在要替换的现有行之前计算。)

如果您希望大多数字符串都是唯一的(即,在大多数情况下,UPDATE 不会执行任何操作),那么先执行 INSERT 并仅在失败时发出 UPDATE 可能会更有效存在唯一约束错误。

但正如 newt 所说,除非您认为自己会超出地址空间,否则哈希会更快。 (即使您超出了可用 RAM,交换也可能比数据库更快。)

【讨论】:

  • hash 不适合,因为字符串大约需要 3Gx10=30GB,我打算在 16GB 的机器上运行。
猜你喜欢
  • 2017-05-30
  • 1970-01-01
  • 2014-01-30
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 1970-01-01
  • 2020-09-02
  • 1970-01-01
相关资源
最近更新 更多