perl dbi mysql - 值精度

Posted

技术标签:

【中文标题】perl dbi mysql - 值精度【英文标题】:perl dbi mysql - value precision 【发布时间】:2020-02-26 15:38:24 【问题描述】:

user@host:~# mysql -V - mysql Ver 14.14 Distrib 5.7.25-28,适用于使用 7.0 在 debian-9,9 下运行的 debian-linux-gnu (x86_64)

user@host:~# uname -a - Linux 4.9.0-8-amd64 #1 SMP Debian 4.9.144-3.1 (2019-02-19) x86_64 GNU/Linux

user@host:~# perl -MDBI -e 'print $DBI::VERSION ."\n";' - 1.636

user@host:~# perl -v 这是为 x86_64-linux-gnu-thread-multi 构建的 perl 5,版本 24,subversion 1 (v5.24.1)

mysql> SHOW CREATE TABLE tbl1;

表1 |创建表`tbl1`( `id` bigint(20) NOT NULL AUTO_INCREMENT, `main_id` bigint(20) NOT NULL DEFAULT '0', `debet` varchar(255) NOT NULL DEFAULT '', `kurs` double(20,4) NOT NULL DEFAULT '0.0000', `summ` double(20,2) NOT NULL DEFAULT '0.00', `is_sync` int(11) NOT NULL DEFAULT '0', 主键(`id`), KEY `main_id` (`main_id`) ) ENGINE=InnoDB AUTO_INCREMENT=70013000018275 默认字符集=utf8

mysql> SELECT * FROM tbl1 WHERE id=70003020040132;

-+---------------+----------------+--------+------- -+---------+----------+ |编号 | main_id |债务 |库尔斯 |总结 | is_sync | +----------------+----------------+--------+----- -+---------+----------+ | 70003020040132 | 70003020038511 | | 0.0000 | 1798.00 | 0 | +----------------+----------------+--------+----- -+---------+----------+

但是当我通过 perl::DBI 模块获取这些数据时,我失去了精度,值 0.00001798.00 变为 01798

接下来是代码:

#### 
#These 3 subs are connecting to DB, executing query and get data by fetchall_arrayref and coverting undef to NULL.
####
sub DB_connect 
    # DataBase Handler
    my $dbh = DBI->connect("DBI:mysql:$DBNAME", $DBUSER, $DBPWD,RaiseError => 0, PrintError => 0, mysql_enable_utf8 => 1) or die "Error connecting to database: $DBI::errstr";
    return $dbh;

sub DB_executeQuery 
    # Executes SQL query. Return reference to array, or array, according to argv[0]
    # argv[0] - "A" returns array, "R" - reference to array
    # argv[1] - DB handler from DB_connect
    # argv[2] - query to execute

    my $choice=shift @_;
    my $dbh=shift @_;
    my $query=shift @_;
    print "$query\n" if $DEBUG>2;
    my $sth=$dbh->prepare($query) or die "Error preparing $query for execution: $DBI::errstr";
    $sth->execute;
    my $retval = $sth->fetchall_arrayref;

    if ($choice eq "A" ) 
    my @ret_arr=();
    foreach my $value (@ $retval ) 
        push @ret_arr,@ $value ;
    
    return @ret_arr;
    
    elsif ($choice eq "R") 
    return $retval;
    


sub undef2null 
    # argv[1] - reference ro array of values where undef
    # values has to be changed to NULL
    # Returns array of prepared values: (...) (...) ...
    my $ref=shift @_;
    my @array=();
    foreach my $row (@ $ref ) 
    my $str="";
    foreach my $val ( @ $row ) 
        if (! defined ( $val )) 
        $str="$str, NULL";
        
        else 
        # Escape quotes and other symbols listed in square brackets
        $val =~ s/([\"\'])/\\$1/g; 
        $str="$str, \'$val\'";
        
    
    # Remove ', ' at the beginning of each VALUES substring
    $str=substr($str,2);
    push @array,"($str)";
     # End foreach my $row (@ $ref_values )
    return @array;
 # End undef2null

#### Main call
#...
# Somewhere in code I get data from DB and print it to out file
my @arr_values=();
my @arr_col_names=DB_executeQuery("A",$dbh,qq(SELECT column_name FROM `information_schema`.`columns` WHERE `table_schema` = '$DBNAME' AND `table_name` = '@ $table '));
@arr_ids=DB_executeQuery("A",$dbh,qq(SELECT `id` FROM `@ $table ` WHERE `is_sync`=0));
my $ref_values=DB_executeQuery("R",$dbh,"SELECT * FROM \`@ $table \` WHERE \`id\` IN(".join(",",@arr_ids).")");
        @arr_values=undef2null($ref_values);
print FOUT  "REPLACE INTO \`@ $table \` (`".join("`, `",@arr_col_names)."`) VALUES  ".(join ", ",@arr_values).";\n";

结果我得到下一个字符串:

替换成`pko_plat`(`id`,`main_id`,`debet`,`kurs`,`summ`,`is_sync`)值('70003020040132','70003020038511','','0', '1798', '0')

在 DB 中是 0.0000 - 变成 0,是 1798.00,变成 1798

Perl 的 DBI 文档说它将数据“按原样”转换为字符串,不进行任何翻译。但是,那么,谁对值进行了四舍五入呢?

【问题讨论】:

尝试插入1798.00001,DBI现在显示什么?它会舍入值吗? 很快 - 是的,它是圆形的。我已经测试过 - 当通过 DBI 从 DB 获取数据时会发生回合。不是通过我使用的其他一些数据处理方法。例如,如果我输入 MySQL 1798.6356,它会舍入到 1798.64,而 DBI 得到 1798.64。如果 1798.00001 - MySQL 显示 1798.00 而 DBI 显示 1798 但我确实需要 1798.00,显示方式与 MySQL 中相同。 hmm.. 你不能只是后处理它吗:$var = sprintf "%.2f\n", $var ? OFC sprintf 打印 1798.00。当我使用 DBI 从 MySQL 获取数据时,直接进行舍入。主要问题是:在使用 DBI 时,我能否以某种方式避免使用数字,而只是将所有数据作为字符串获取(如 DBI 文档中所述)? 抱歉,根据您上面的评论,在我看来是 MySQL(不是 DBI)将 1798.6356 舍入到 1798.64? 【参考方案1】:

您看到的舍入是由于您创建列的方式而发生的。

  `kurs` double(20,4) NOT NULL DEFAULT '0.0000'
  `summ` double(20,2) NOT NULL DEFAULT '0.00'

如果您查看mysql floating point type documentation,您会发现您使用的是非标准语法double(m, d),其中两个参数定义了浮点数的输出方式。

因此,在您的情况下,summ 中存储的值将显示在该点后面 2 位。这意味着当 perl 从数据库中的表中获取一个值 1.0001 时,perl 得到的数据库传递的值将四舍五入到设置的位数(在本例中为 .00)。

Perl 反过来将此值(“1.00”)解释为浮点数,并且在打印时不会显示任何尾随零。如果你想要这些,你应该在你的输出中适应这个。

例如:print sprintf("%.2f\n", $summ);

在我看来,你有两种方法可以走(如果你想避免这种精度损失):

仅将具有正确精度的数字添加到数据库中(因此 'summ' 只需要两个尾随数字,'kurs' 需要四个数字。) 将您的表创建更改为浮点的标准语法并确定 Perl 中的输出格式(您将采用任何一种方式):
`kurs` double() NOT NULL DEFAULT '0.0'

【讨论】:

非常感谢大家的信息和帮助。这个关于使用 DBI 获取数据和舍入的问题是因为我们已经有 Python 脚本在做这些事情,并且它获取的数据与 mysql 客户端显示的相同('0.00' 显示为 '0.00',而不是 '0')。我不知道,我们的其他应用程序(C#、php 等)将如何处理这些“圆形”数据(向我们的开发人员致敬!)。所以我尝试使用 DBI 创建相同的行为。 您对这个 python 脚本如何处理数据库中的数据有任何见解吗?也许它一开始就没有将其转换为数字类型? 此表的配置方式,除非数据库连接器有直接获取浮点数的方法,否则您将始终获取格式化的四舍五入值。此外,这种在 mysql 中设置浮点类型(定义输出)的方式已被弃用,因此在创建表时切换到标准语法确实是更好的选择。 我对Python一点也不熟悉,但是有这个脚本的源代码。并找到下一个代码:def make_query_head(self): self.query_head = "REPLACE INTO 0` (1) VALUES ".format( self.table, ", ".join(map(lambda x: "%s" % x, self.fields)) )` - 这里似乎进行了转换? 你能发布从数据库中获取数据的python代码吗?在我看来,获取的数据从未转换为相应的数字类型。 python 代码print("%s" % (2.0000)) 将打印2.0,所以要么你永远不会有两个尾随零,要么在输出时该值是一个字符串。

以上是关于perl dbi mysql - 值精度的主要内容,如果未能解决你的问题,请参考以下文章

Perl 连接 MySQL,DBI怎么安装?

在 Windows-7-x64 上使用 DBI Perl 和 MySql 的未定义 $DBI::errstr

perl dbi mysql 向表中插入数据速度很慢

Linux中perl-DBI和DBI有啥区别?

解释 Perl DBI MySQL column_info()

Perl DBI:如何使用绑定值查看失败的查询?