【问题标题】:How can I tell DBD::CSV to use a comma as the decimal seperator?如何告诉 DBD::CSV 使用逗号作为小数分隔符?
【发布时间】:2012-11-24 11:16:34
【问题描述】:

我正在尝试使用带有 DBI 和 DBD::CSV 的德式 CSV 文件。这反过来又使用Text::CSV 来解析文件。我想使用 SQL 查询该文件中的数据。

让我们先看一下文件。用分号(;)隔开,里面的数字是这样的:5,23,相当于英文的5.23

这是我目前所得到的:

use strict; use warnings;
use DBI;

# create the database handle
my $dbh = DBI->connect(
  'dbi:CSV:',
  undef, undef,
  {
    f_dir => '.',
    f_schema => undef,
    f_ext => '.csv',
    f_encoding => 'latin-1',
    csv_eol => "\n",
    csv_sep_char => ';',
    csv_tables => {
      foo => {
        file => 'foo.csv',
        #skip_first_row => 0,
        col_names => [ map { "col$_" } (1..3)  ], # see annotation below
      },
    },
  },
) or croak $DBI::errstr;

my $sth = $dbh->prepare(
  'SELECT col3 FROM foo WHERE col3 > 80.50 ORDER BY col3 ASC'
);
$sth->execute;

while (my $res = $sth->fetchrow_hashref) {
  say $res->{col3};
}

现在,这看起来很不错。问题是 SQL(意思是 SQL::Statement,它位于 DBI 和 DBD::CSV 后面的某个位置)不考虑 col3 中的数据,这是一个中间带逗号的浮点值, 作为一个浮点数。相反,它将列视为整数,因为它不理解逗号。

以下是一些示例数据:

foo;foo;81,90
bar;bar;80,50
baz;baz;80,70

所以上面的代码和这个数据会产生一行输出:81,90。当然,这是错误的。它使用col3int() 部分进行比较,这是正确的,但不是我想要的。

问题:如何告诉它把带逗号的数字视为浮点数?

我想到的事情:

  • 我没有在 Text::CSV 中找到任何内置方法来执行此操作。我不确定在 Text::CSV 中的哪个位置可以将其挂钩,或者 Text::CSV 中是否有机制可以将这些东西放入其中。
  • 不知道DBD::CSV想尽可能使用Text::CSV_XS是否会造成问题。
  • 也许我可以稍后再做,在读取数据并且已经存储在某个地方之后,但我还不确定正确的访问点在哪里。
  • 我知道这些内容存储在 SQL::Statement 中。我还不知道在哪里。这可能会很方便。

将源 CSV 文件更改为使用点而不是逗号是不是一种选择。

我愿意接受各种建议。也欢迎通过 SQL 处理整个 CSV 的其他方法。非常感谢。

【问题讨论】:

  • 您绝对需要通过 SQL 访问这些数据吗?
  • 你说的这个“它”是谁? “它不理解逗号”,“它使用了 col3 的 int() 部分”...... Perl?一个模块?数据库?
  • @Borodin 的总体思路是拥有多个数据源。我们正在尝试将 CSV 文件从 opening 和 <>ing 升级到基于 DB 的方法,其中 DB 将是 mysql 或 sqlite。第一步应该是构建 SQL,但使用 CSV 文件。我们不想破坏 CSV 文件涉及的其他进程。所以,是的,我想用 SQL 来做。
  • @TLP 这将是一些涉及执行查询的模块。可能在 DBI 中。目标是让该查询发挥作用。
  • @simbabque 也许可以通过使用小数点的德语区域设置以某种方式完成? POSIX 模块?

标签: perl csv dbi


【解决方案1】:

您需要使用SQL::Statement::Functions 编写一个用户定义的函数(已作为DBD::CSV 的一部分加载)。

这个程序做你想做的事。在转换后的字符串中添加0.0 是完全没有必要的,但它说明了子例程的目的。 (另请注意您在 connect 调用中的 f_encoding 参数中的拼写错误。)

use strict;
use warnings;

use DBI;

my $dbh = DBI->connect(
  'dbi:CSV:',
  undef, undef,
  {
    f_dir => '.',
    f_schema => undef,
    f_ext => '.csv',
    f_encoding => 'latin-1',
    csv_eol => "\n",
    csv_sep_char => ';',
    csv_tables => {
      foo => {
        file => 'test.csv',
        #skip_first_row => 0,
        col_names => [ map { "col$_" } (1..3)  ], # see annotation below
      },
    },
  },
) or croak $DBI::errstr;

$dbh->do('CREATE FUNCTION comma_float EXTERNAL');

sub comma_float {
  my ($self, $sth, $n) = @_;
  $n =~ tr/,/./;
  return $n + 0.0;
}

my $sth = $dbh->prepare(
  'SELECT col3 FROM foo WHERE comma_float(col3) > 80.50 ORDER BY col3 ASC'
);
$sth->execute;

while (my $res = $sth->fetchrow_hashref) {
  say $res->{col3};
}

输出

80,70
81,90

【讨论】:

  • 非常感谢!这很棒,我不知道这是可能的。不过,这意味着在每个查询中为每一行调用该函数吗?我做了一个快速基准测试,它将prepareexecute 的持续时间从 1.1 秒增加到 1.6 秒。我相信从长远来看,更改底层 SQL::Statement::RAM::Table 会更快。有什么想法吗?
  • 我不相信prepare 需要 1.6 ?您是否正在计时多次执行?优化的黄金法则是在你发现你需要之前不要这样做。无论如何,延迟增加 45% 并不是那么好:等待 15 分钟而不是 10 分钟没什么大不了的。看起来SQL::Statement::RAM 是供内部使用的,我怀疑您是否可以通过这种方式执行与CREATE TEMP TABLE footemp AS SELECT col1, col2, comma_float(col3) 不同的任何操作。
  • 我没有计时多次执行。 my $t0 = Benchmark->new; my $sth = $dbh->prepare( 'SELECT col2, col3 FROM artikel WHERE comma_float(col3) > 90.50 ORDER BY col3 ASC' ); $sth->execute; my $t1 = Benchmark->new; my $sth2 = $dbh->prepare( 'SELECT col2, col3 FROM artikel WHERE col3 > 90.50 ORDER BY col3 ASC' ); $sth2->execute; my $t2 = Benchmark->new; say timestr(timediff($t1, $t0)); say timestr(timediff($t2, $t1));丑,我知道。它说:1.6931 wallclock secs ( 1.12 usr + 0.06 sys = 1.19 CPU) 1.16507 wallclock secs ( 0.69 usr + 0.05 sys = 0.73 CPU) 我的文件大约有 20k 行。
  • @simbabque:嗯,我假设您使用的是问题中的代码和数据 :) 对于 30,000 条记录的文件,我得到 1.6 秒和 1.8 秒。您的预期数据大小是多少?我原以为不到两秒钟就可以了。这个问题似乎更多地与DBD::CSV 有关,而不是用户定义的函数——你不能指望从 CSV 文件中获得高速!如果这是一个游戏破坏者,您应该将 CSV 文件读入 SQLite 表并从那里开始工作。
  • @simbabque:您应该设置一个timethese 来正确比较速度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-14
  • 2014-07-16
  • 1970-01-01
  • 2011-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多