perl dbi mysql - точность значения - PullRequest
0 голосов
/ 31 октября 2019

user @ host: ~ # mysql -V - mysql Ver 14.14 Distrib 5.7.25-28, для debian-linux-gnu (x86_64), использующего 7.0 под управлением debian-9,9

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 Это perl 5, версия 24, subversion1 (v5.24.1) для x86_64-linux-gnu-thread-multi

mysql> SHOW CREATE TABLE tbl1;

tbl1 | CREATE TABLE `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',
  PRIMARY KEY (`id`),
  KEY `main_id` (`main_id`)
  ) ENGINE=InnoDB AUTO_INCREMENT=70013000018275 DEFAULT CHARSET=utf8 

mysql> SELECT * FROM tbl1 WHERE id=70003020040132;

-+---------------+----------------+-------+--------+---------+---------+
| id             | main_id        | debet | kurs   | summ    | is_sync | 
+----------------+----------------+-------+--------+---------+---------+
| 70003020040132 | 70003020038511 |       | 0.0000 | 1798.00 | 0       |
+----------------+----------------+-------+--------+---------+---------+

Но когда я получаю эти данные с помощью модуля perl :: DBI, я теряю точность, и значения 0.0000 и 1798.00 становятся 0 и 1798.

Код следующий:

#### 
#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";

и в результате я получаю следующую строку:

REPLACE INTO `pko_plat` (`id`, `main_id`, `debet`, `kurs`, `summ`, `is_sync`) VALUES  ('70003020040132', '70003020038511', '', '0', '1798', '0')

в БД это было 0.0000- становиться 0, было 1798.00, становиться 1798

Документация Perl DBI гласит, что он получает данные «как есть» в строки, без перевода. Но тогда кто округлил значения?

1 Ответ

1 голос
/ 01 ноября 2019

Округление, которое вы видите, происходит из-за способа создания столбцов.

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

Если вы посмотрите документацию типа mysql с плавающей запятой , вы увидите, что вы используетенестандартный синтаксис double(m, d), где два параметра определяют способ вывода числа с плавающей запятой.

Таким образом, в вашем случае значения, хранящиеся в summ, будут отображаться с двумя цифрами за точкой. Это означает, что когда perl получает значение из таблицы, равное 1.0001 в базе данных, значение, которое perl получает от базы данных, округляется до установленного количества цифр (в данном случае .00).

Perl, в свою очередь, интерпретирует это значение («1,00») как число с плавающей запятой, и при выводе на печать не будет показывать завершающие нули. Если вы хотите это, вы должны учесть это в своем выводе.

Например: print sprintf("%.2f\n", $summ);

На мой взгляд, у вас есть два пути (если вы хотите избежать этой потери точности):

  • Добавляйте в базу данных только числа с правильной точностью (поэтому для 'summ' только две конечные цифры и четыре для 'kurs'.)
  • Измените создание таблицы на стандартный синтаксис для чисел с плавающей запятой и определите выводформатирование в Perl (что вы будете делать в любом случае):
`kurs` double() NOT NULL DEFAULT '0.0'
...