我从客户端收到一些CSV。这些CSV的平均大小为20 MB。
格式为:
Cutomer1,Product1,cat1,many,other,info
Cutomer1,Product2,cat1,many,other,info
Cutomer1,Product2,cat2,many,other,info
Cutomer1,Product3,cat1,many,other,info
Cutomer1,Product3,cat7,many,other,info
Cutomer2,Product5,cat1,many,other,info
Cutomer2,Product5,cat1,many,other,info
Cutomer2,Product5,cat4,many,other,info
Cutomer3,Product7,cat,many,other,info
我目前的做法: 我将所有这些记录临时存储在一个表中,然后在表中查询:
where customer='customer1' and product='product1'
where customer='customer1' and product='product2'
where customer='customer2' and product='product1'
问题:在数据库中插入然后选择花费太多时间。很多事情正在发生,处理一个CSV需要10-12分钟。我目前正在使用SQLite,它非常快。但是如果我完全删除插入和选择,我想我会节省更多时间。
我想知道是否可以将这个完整的CSV存储在一些复杂的perl数据结构中?
机器通常有500MB +空闲RAM。
答案 0 :(得分:2)
如果您显示的查询是您想要执行的唯一一种查询,那么这很简单。
my $orders; # I guess
while (my $row = <DATA> ) {
chomp $row;
my @fields = split /,/, $row;
push @{ $orders->{$fields[0]}->{$fields[1]} } \@fields; # or as a hashref, but that's larger
}
print join "\n", @{ $orders->{Cutomer1}->{Product1}->[0] }; # typo in cuStomer
__DATA__
Cutomer1,Product1,cat1,many,other,info
Cutomer1,Product2,cat1,many,other,info
Cutomer1,Product2,cat2,many,other,info
Cutomer1,Product3,cat1,many,other,info
Cutomer1,Product3,cat7,many,other,info
Cutomer2,Product5,cat1,many,other,info
Cutomer2,Product5,cat1,many,other,info
Cutomer2,Product5,cat4,many,other,info
Cutomer3,Product7,cat,many,other,info
您只需构建一个深度为几级的哈希引用的索引。第一级有客户。它包含另一个hashref,它具有与此索引匹配的行列表。然后你可以决定你是否只想把整个东西作为一个数组引用,或者如果你想在那里放一个带有键的哈希引用。我使用数组引用,因为它消耗更少的内存。
稍后您可以轻松查询。我把上面包括在内。这是输出。
Cutomer1
Product1
cat1
many
other
info
如果您不想记住索引但必须编写许多不同的查询,您可以创建代表magic numbers的变量(甚至常量)。
use constant {
CUSTOMER => 0,
PRODUCT => 1,
CATEGORY => 2,
MANY => 3,
OTHER => 4,
INFO => 5,
};
# build $orders ...
my $res = $orders->{Cutomer1}->{Product2}->[0];
print "Category: " . $res->[CATEGORY];
输出结果为:
Category: cat2
要订购结果,您可以使用Perl&#39; sort
。如果您需要按两列排序,那么SO上有答案可以解释如何执行此操作。
for my $res (
sort { $a->[OTHER] cmp $b->[OTHER] }
@{ $orders->{Customer2}->{Product1} }
) {
# do stuff with $res ...
}
但是,您只能按照客户和产品进行搜索。
如果有多种类型的查询,这会变得很昂贵。如果您还要按类别对它们进行分组,则每次查看一次时都要对所有这些进行迭代,或者构建第二个索引。这样做比等待几秒钟更难,所以你可能不想这样做。
我想知道是否可以将这个完整的CSV存储在一些复杂的perl数据结构中?
绝对是出于这个目的。 20兆字节不是很多。
我已经使用此代码创建了一个20004881字节和447848行的测试文件,但这并不完美,但可以完成工作。
use strict;
use warnings;
use feature 'say';
use File::stat;
open my $fh, '>', 'test.csv' or die $!;
while ( stat('test.csv')->size < 20_000_000 ) {
my $customer = 'Customer' . int rand 10_000;
my $product = 'Product' . int rand 500;
my $category = 'cat' . int rand 7;
say $fh join ',', $customer, $product, $category, qw(many other info);
}
以下是该文件的摘录:
$ head -n 20 test.csv
Customer2339,Product176,cat0,many,other,info
Customer2611,Product330,cat2,many,other,info
Customer1346,Product422,cat4,many,other,info
Customer1586,Product109,cat5,many,other,info
Customer1891,Product96,cat5,many,other,info
Customer5338,Product34,cat6,many,other,info
Customer4325,Product467,cat6,many,other,info
Customer4192,Product239,cat0,many,other,info
Customer6179,Product373,cat2,many,other,info
Customer5180,Product302,cat3,many,other,info
Customer8613,Product218,cat1,many,other,info
Customer5196,Product71,cat5,many,other,info
Customer1663,Product393,cat4,many,other,info
Customer6578,Product336,cat0,many,other,info
Customer7616,Product136,cat4,many,other,info
Customer8804,Product279,cat5,many,other,info
Customer5731,Product339,cat6,many,other,info
Customer6865,Product317,cat2,many,other,info
Customer3278,Product137,cat5,many,other,info
Customer582,Product263,cat6,many,other,info
现在让我们使用这个输入文件运行上面的程序,查看内存消耗和数据结构大小的一些统计信息。
use strict;
use warnings;
use Devel::Size 'total_size';
use constant {
CUSTOMER => 0,
PRODUCT => 1,
CATEGORY => 2,
MANY => 3,
OTHER => 4,
INFO => 5,
};
open my $fh, '<', 'test.csv' or die $!;
my $orders;
while ( my $row = <$fh> ) {
chomp $row;
my @fields = split /,/, $row;
$orders->{ $fields[0] }->{ $fields[1] } = \@fields;
}
say 'total size of $orders: ' . total_size($orders);
这是:
total size of $orders: 185470864
因此该变量消耗185兆字节。这远远超过20MB的CSV,但我们有一个易于搜索的索引。使用htop我发现实际过程消耗287MB。我的机器有16G的内存,所以我不在乎。使用大约3.6秒,运行该程序的速度相当快,但我有一台SSD是一台新的CORE i7机器。
但是如果你有500MB的空余,它就不会占用你的全部记忆。可能一种SQLite方法会占用更少的内存,但是你必须将这种速度与SQLite方法的速度进行比较,以确定哪一种更适合。
我使用方法described in this answer将文件读入SQLite数据库 1 。我需要先在文件中添加一个标题行,但这很简单。
$ sqlite3 test.db
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
sqlite> .mode csv test
sqlite> .import test.csv test
由于我无法正确衡量这一点,因此我们说感觉就像大约2秒钟。然后我为特定查询添加了一个索引。
sqlite> CREATE INDEX foo ON test ( customer, product );
这感觉好像花了一秒钟。现在我可以查询。
sqlite> SELECT * FROM test WHERE customer='Customer23' AND product='Product1';
Customer23,Product1,cat2,many,other,info
结果瞬间出现(这不科学!)。由于我们没有测量从Perl数据结构中检索的时间长短,我们无法对它们进行比较,但感觉这一切都需要大约相同的时间。
但是,SQLite文件大小仅为38839296,大约为39MB。它比CSV文件大,但不是很多。看起来sqlite3进程只消耗了大约30kB的内存,考虑到索引,我觉得这很奇怪。
总之,SQLite似乎更方便,占用的内存更少。在Perl中执行此操作没有任何问题,它可能是相同的速度,但使用SQL进行此类查询感觉更自然,所以我会选择这个。
如果我可能如此大胆,我会假设你在SQLite中没有设置索引,这使得它需要更长的时间。我们这里的行数并不多,即使对于SQLite也是如此。正确地指出它是件小事。
如果您实际上并不知道索引的作用,请考虑一下电话簿。它具有页面两侧的首字母索引。找到John Doe,你抓住D,然后以某种方式看。现在想象没有这样的事情。你需要随意地捅更多。然后尝试找到电话号码为123-555-1234的家伙。如果没有索引,这就是您的数据库所做的事情。
1)如果你想编写脚本,你也可以将命令管道或读入sqlite3
实用程序来创建数据库,然后使用Perl的DBI进行查询。例如,sqlite3 foo.db <<<'.tables\ .tables'
(反斜杠\
表示文字换行符)会打印两次表格列表,因此这样的导入也会起作用。