From a389b426e4235584ff160bac044033cf88655512 Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Fri, 13 Jan 2023 20:58:50 +0000 Subject: [PATCH] 93_DbRep: new averageValue option writeToDBSingleStart git-svn-id: https://svn.fhem.de/fhem/trunk@27047 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/93_DbRep.pm | 809 +++++++++++++++++++++++++----------------- 2 files changed, 478 insertions(+), 332 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 838385183..49338c304 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 93_DbRep: new averageValue option writeToDBSingleStart - bugfix: 72_FRITZBOX: Fehlerbereinigungen u.a. "," Reading - change: 49_SSCamSTRM: change behavior of hideDisplayName, hideDisplayNameFTUI if device is disabled diff --git a/fhem/FHEM/93_DbRep.pm b/fhem/FHEM/93_DbRep.pm index abd5fa7d9..3cdd65891 100644 --- a/fhem/FHEM/93_DbRep.pm +++ b/fhem/FHEM/93_DbRep.pm @@ -59,7 +59,8 @@ no if $] >= 5.017011, warnings => 'experimental::smartmatch'; # Version History intern my %DbRep_vNotesIntern = ( - "8.51.1" => "01.01.2023 write TYPE uppercase with writeToDB option, Commandref edited, fix add SQL Cache History ". + "8.51.2" => "13.01.2023 rewrite sub DbRep_OutputWriteToDB, new averageValue option writeToDBSingleStart ", + "8.51.1" => "11.01.2023 write TYPE uppercase with writeToDB option, Commandref edited, fix add SQL Cache History ". "set PRAGMA auto_vacuum = FULL when execute SQLite vacuum command", "8.51.0" => "02.01.2023 online formatting of sqlCmd, sqlCmdHistory, sqlSpecial, Commandref edited, get dbValue removed ". "sqlCmdBlocking customized like sqlCmd, bugfix avgTimeWeightMean ", @@ -641,7 +642,7 @@ sub DbRep_Set { (($hash->{ROLE} ne "Agent") ? "tableCurrentPurge:noArg " : ""). (($hash->{ROLE} ne "Agent") ? "countEntries:history,current " : ""). (($hash->{ROLE} ne "Agent") ? "sumValue:display,writeToDB,writeToDBSingle,writeToDBInTime " : ""). - (($hash->{ROLE} ne "Agent") ? "averageValue:display,writeToDB,writeToDBSingle,writeToDBInTime " : ""). + (($hash->{ROLE} ne "Agent") ? "averageValue:display,writeToDB,writeToDBSingle,writeToDBSingleStart,writeToDBInTime " : ""). (($hash->{ROLE} ne "Agent") ? "delSeqDoublets:adviceRemain,adviceDelete,delete " : ""). (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/) ? "dumpMySQL:clientSide,serverSide " : ""). (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE/) ? "dumpSQLite:noArg " : ""). @@ -3508,7 +3509,7 @@ sub DbRep_averval { } $dt = $tn - $to; - $sum += $val1*($dt/$tsum) if($tsum); + $sum += $val1 * ($dt/$tsum) if($tsum); $val1 = $val; $to = $tn; @@ -3518,25 +3519,31 @@ sub DbRep_averval { } my $aval = (DbRep_checktimeaggr($hash))[2]; + if($aval eq "hour") { - @rsf = split(/[ :]/,$runtime_string_first); - @rsn = split(/[ :]/,$runtime_string_next); + @rsf = split /[ :]/,$runtime_string_first; + @rsn = split /[ :]/,$runtime_string_next; $arrstr .= $runtime_string."#".$sum."#".$rsf[0]."_".$rsf[1]."|"; } elsif ($aval eq "minute") { - @rsf = split(/[ :]/,$runtime_string_first); - @rsn = split(/[ :]/,$runtime_string_next); + @rsf = split /[ :]/,$runtime_string_first; + @rsn = split /[ :]/,$runtime_string_next; $arrstr .= $runtime_string."#".$sum."#".$rsf[0]."_".$rsf[1]."-".$rsf[2]."|"; } else { - @rsf = split(" ",$runtime_string_first); - @rsn = split(" ",$runtime_string_next); + @rsf = split " ",$runtime_string_first; + @rsn = split " ",$runtime_string_next; $arrstr .= $runtime_string."#".$sum."#".$rsf[0]."|"; } - @wsf = split(" ",$runtime_string_first); - @wsn = split(" ",$runtime_string_next); - $wrstr .= $runtime_string."#".$sum."#".$wsf[0]."_".$wsf[1]."#".$wsn[0]."_".$wsn[1]."|"; # Kombi zum Rückschreiben in die DB + #@wsf = split " ",$runtime_string_first; + #@wsn = split " ",$runtime_string_next; + + $runtime_string_first =~ s/\s/_/xs; + $runtime_string_next =~ s/\s/_/xs; + + #$wrstr .= $runtime_string."#".$sum."#".$wsf[0]."_".$wsf[1]."#".$wsn[0]."_".$wsn[1]."|"; # Kombi zum Rückschreiben in die DB + $wrstr .= $runtime_string."#".$sum."#".$runtime_string_first."#".$runtime_string_next."|"; # Kombi zum Rückschreiben in die DB } } @@ -4352,7 +4359,7 @@ sub DbRep_diffval { $runtime_string = encode_base64($runtime_string,""); if($dbmodel eq "OLDMYSQLVER") { # Forum: https://forum.fhem.de/index.php/topic,130697.0.html - $err = DbRep_dbhDo ($name, $dbh, "set \@V:= 0, \@diff:= 0, \@diffTotal:= 0, \@RB:= 1;"); # @\RB = Resetbit wenn neues Selektionsintervall beginnt + ($err, undef) = DbRep_dbhDo ($name, $dbh, "set \@V:= 0, \@diff:= 0, \@diffTotal:= 0, \@RB:= 1;"); # @\RB = Resetbit wenn neues Selektionsintervall beginnt return "$name|$err" if ($err); } @@ -4969,13 +4976,13 @@ sub DbRep_insert { my ($sth,$sql,$irow); # insert into $table mit/ohne primary key - if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') { + if ($usepkh && $dbmodel eq 'MYSQL') { $sql = "INSERT IGNORE INTO $table (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"; } - elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') { + elsif ($usepkh && $dbmodel eq 'SQLITE') { $sql = "INSERT OR IGNORE INTO $table (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"; } - elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') { + elsif ($usepkh && $dbmodel eq 'POSTGRESQL') { $sql = "INSERT INTO $table (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"; } else { @@ -6883,7 +6890,7 @@ sub _DbRep_setSessAttrVars { $pm = ltrim($pm).';'; $pm =~ s/ESC_ESC_ESC/;/gx; # wiederherstellen von escapeten ";" -> umwandeln von ";;" in ";" - my $err = DbRep_dbhDo ($name, $dbh, $pm, "Set VARIABLE or PRAGMA: $pm"); + (my $err, undef) = DbRep_dbhDo ($name, $dbh, $pm, "Set VARIABLE or PRAGMA: $pm"); return $err if ($err); } } @@ -6913,7 +6920,7 @@ sub _DbRep_setSessVars { $pm = ltrim($pm).';'; $pm =~ s/ESC_ESC_ESC/;/gx; # wiederherstellen von escapeten ";" -> umwandeln von ";;" in ";" - my $err = DbRep_dbhDo ($name, $dbh, $pm, "Set SQL session variable: $pm"); + (my $err, undef) = DbRep_dbhDo ($name, $dbh, $pm, "Set SQL session variable: $pm"); return $err if ($err); } } @@ -6943,7 +6950,7 @@ sub _DbRep_setSessPragma { $pm = ltrim($pm).';'; $pm =~ s/ESC_ESC_ESC/;/gx; # wiederherstellen von escapeten ";" -> umwandeln von ";;" in ";" - my $err = DbRep_dbhDo ($name, $dbh, $pm, "Exec PRAGMA Statement: $pm"); + (my $err, undef) = DbRep_dbhDo ($name, $dbh, $pm, "Exec PRAGMA Statement: $pm"); return $err if ($err); } } @@ -6975,7 +6982,7 @@ sub _DbRep_execSessPrepare { $pm = ltrim($pm).';'; $pm =~ s/ESC_ESC_ESC/;/gx; # wiederherstellen von escapeten ";" -> umwandeln von ";;" in ";" - my $err = DbRep_dbhDo ($name, $dbh, $pm, "Exec PREPARE statement: $pm"); + (my $err, undef) = DbRep_dbhDo ($name, $dbh, $pm, "Exec PREPARE statement: $pm"); return $err if ($err); } } @@ -7214,7 +7221,7 @@ sub DbRep_dbmeta { # due to incompatible changes made in MyQL 5.7.5, see http://johnemb.blogspot.de/2014/09/adding-or-removing-individual-sql-modes.html if($dbmodel eq "MYSQL") { - $err = DbRep_dbhDo ($name, $dbh, "SET sql_mode=(SELECT REPLACE(\@\@sql_mode,'ONLY_FULL_GROUP_BY',''));"); + ($err, undef) = DbRep_dbhDo ($name, $dbh, "SET sql_mode=(SELECT REPLACE(\@\@sql_mode,'ONLY_FULL_GROUP_BY',''));"); return "$name|$err" if ($err); } @@ -11000,6 +11007,43 @@ sub DbRep_resolveDevspecs { return ($devs,$devswc); } +###################################################################################### +# Erstelle Insert SQL-Schema für Tabelle mit/ohne primary key +###################################################################################### +sub DbRep_createInsertSQLscheme { + my $table = shift; + my $dbmodel = shift; + my $usepkh = shift; + + my $sql; + + if ($usepkh && $dbmodel eq 'MYSQL') { + $sql = "INSERT IGNORE INTO $table (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"; + } + elsif ($usepkh && $dbmodel eq 'SQLITE') { + $sql = "INSERT OR IGNORE INTO $table (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"; + } + elsif ($usepkh && $dbmodel eq 'POSTGRESQL') { + $sql = "INSERT INTO $table (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"; + } + else { + $sql = "INSERT INTO $table (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"; + } + +return $sql; +} + +###################################################################################### +# Erstelle Update SQL-Schema für Tabelle +###################################################################################### +sub DbRep_createUpdateSQLscheme { + my $table = shift; + + my $sql = "UPDATE $table SET TIMESTAMP=?, DEVICE=?, READING=?, TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE TIMESTAMP=? AND DEVICE=? AND READING=?"; + +return $sql; +} + ###################################################################################### # Connect zur Datenbank herstellen # @@ -11147,7 +11191,7 @@ return ($err, $sth, $result); } #################################################################################################### -# einfaches Sdbh->do, return ERROR-String wenn Fehler +# einfaches Sdbh->do, return ERROR-String wenn Fehler bzw. die Anzahl der betroffenen Zeilen #################################################################################################### sub DbRep_dbhDo { my $name = shift; @@ -11156,19 +11200,79 @@ sub DbRep_dbhDo { my $info = shift // "simple do statement: $sql"; my $err = q{}; - - my $sth; + my $rv = q{}; Log3 ($name, 4, "DbRep $name - $info"); - eval{ $dbh->do($sql); + eval{ $rv = $dbh->do($sql); + 1; } or do { $err = encode_base64($@,""); Log3 ($name, 2, "DbRep $name - ERROR - $@"); $dbh->disconnect; }; -return $err; +return ($err, $rv); +} + +#################################################################################################### +# führt ein sth execute prepared Insert aus +# return ERROR oder die Anzahl der betroffenen Zeilen +#################################################################################################### +sub DbRep_execInsertPrepared { + my $paref = shift; + + my $name = $paref->{name}; + my $sth = $paref->{sth}; + my $timestamp = $paref->{timestamp}; + my $device = $paref->{device}; + my $type = $paref->{type}; + my $event = $paref->{event}; + my $reading = $paref->{reading}; + my $value = $paref->{value}; + my $unit = $paref->{unit}; + my $err = q{}; + my $rv = q{}; + + eval{ $rv = $sth->execute($timestamp, $device, $type, $event, $reading, $value, $unit); + } + or do { $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - ERROR - $@"); + }; + + $rv = 0 if($rv eq "0E0"); + +return ($err, $rv); +} + +#################################################################################################### +# führt ein sth execute prepared Update aus +# return ERROR oder die Anzahl der betroffenen Zeilen +#################################################################################################### +sub DbRep_execUpdatePrepared { + my $paref = shift; + + my $name = $paref->{name}; + my $sth = $paref->{sth}; + my $timestamp = $paref->{timestamp}; + my $device = $paref->{device}; + my $type = $paref->{type}; + my $event = $paref->{event}; + my $reading = $paref->{reading}; + my $value = $paref->{value}; + my $unit = $paref->{unit}; + my $err = q{}; + my $rv = q{}; + + eval{ $rv = $sth->execute($timestamp, $device, $reading, $type, $event, $value, $unit, $timestamp, $device, $reading); + } + or do { $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - ERROR - $@"); + }; + + $rv = 0 if($rv eq "0E0"); + +return ($err, $rv); } #################################################################################################### @@ -12695,31 +12799,32 @@ return \%ncp; # Funktionsergebnisse in Datenbank schreiben #################################################################################################### sub DbRep_OutputWriteToDB { - my ($name,$device,$reading,$wrstr,$optxt) = @_; + my $name = shift; + my $device = shift; + my $reading = shift; + my $wrstr = shift; + my $optxt = shift; # Operation Kürzel + my $hash = $defs{$name}; my $dbloghash = $defs{$hash->{HELPER}{DBLOGDEVICE}}; - my $dbconn = $dbloghash->{dbconn}; - my $dbuser = $dbloghash->{dbuser}; my $dblogname = $dbloghash->{NAME}; - my $dbmodel = $dbloghash->{MODEL}; - my $DbLogType = AttrVal($dblogname, "DbLogType", "History"); - my $supk = AttrVal($dblogname, "noSupportPK", 0); - my $dbpassword = $attr{"sec$dblogname"}{secret}; - my $utf8 = $hash->{UTF8} // 0; + my $DbLogType = AttrVal ($dblogname, 'DbLogType', 'History'); + my $supk = AttrVal ($dblogname, 'noSupportPK', 0); + $device =~ s/[^A-Za-z\/\d_\.-]/\//g; $reading =~ s/[^A-Za-z\/\d_\.-]/\//g; - my $type = "calculated"; - my $event = "calculated"; - my $unit = ""; + my $type = 'calculated'; + my $event = 'calculated'; + my $unit = qq{}; my $wrt = 0; my $irowdone = 0; - my $ndp = AttrVal($name, "numDecimalPlaces", $dbrep_defdecplaces); + my $ndp = AttrVal ($name, 'numDecimalPlaces', $dbrep_defdecplaces); - my ($dbh,$sth_ih,$sth_uh,$sth_ic,$sth_uc,$err,$value,$date,$time,$hour,$minute,$ndate,$ntime,$rsf,$rsn,@row_array); + my ($dbh,$err,$value,$date,$time,$hour,$minute,$ndate,$ntime,$rsf,$rsn,@wr_arr); my ($timestamp,$year,$mon,$mday,$t1,$corr); if(!$dbloghash->{HELPER}{COLSET}) { - $err = "No result of \"$hash->{LASTCMD}\" to database written. Cause: column width in \"$hash->{DEF}\" isn't set"; + $err = qq(No result of "$hash->{LASTCMD}" to database written. Cause: column width in "$hash->{DEF}" isn't set); Log3 ($name, 2, "DbRep $name - ERROR - $err"); @@ -12730,52 +12835,47 @@ sub DbRep_OutputWriteToDB { no warnings 'uninitialized'; - my $aggr = (DbRep_checktimeaggr($hash))[2]; - $reading = $optxt."_".$aggr."_".AttrVal($name, "readingNameMap", $reading); + my $aggr = (DbRep_checktimeaggr ($hash))[2]; + $reading = $optxt."_".$aggr."_".AttrVal ($name, 'readingNameMap', $reading); $type = uc($defs{$device}{TYPE}) if($defs{$device}); # $type vom Device übernehmen if($optxt =~ /avg|sum/) { - my @arr = split("\\|", $wrstr); + my @arr = split "\\|", $wrstr; my $ele = $#arr; # Nr des letzten Elements my $i = 0; for my $row (@arr) { - my @a = split("#", $row); + my @a = split "#", $row; my $runtime_string = $a[0]; # Aggregations-Alias (nicht benötigt) $value = defined($a[1]) ? (looks_like_number($a[1]) ? sprintf("%.${ndp}f",$a[1]) : undef) : undef; # in Version 8.40.0 geändert $rsf = $a[2]; # Runtime String first - Datum / Zeit für DB-Speicherung - ($date,$time) = split("_",$rsf); + ($date,$time) = split "_", $rsf; $time =~ s/-/:/g if($time); $rsn = $a[3]; # Runtime String next - Datum / Zeit für DB-Speicherung - ($ndate,$ntime) = split("_",$rsn); + ($ndate,$ntime) = split "_", $rsn; $ntime =~ s/-/:/g if($ntime); if($aggr =~ /no|day|week|month|year/) { - $time = "00:00:01" if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single)*?\b/); # https://forum.fhem.de/index.php/topic,105787.msg1013920.html#msg1013920 - $ntime = "23:59:59" if($ntime !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single)*?\b/); - ($year,$mon,$mday) = split("-", $ndate); - $corr = ($i != $ele) ? 86400 : 0; + $time = "00:00:01" if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single(Start)?)*?\b/); # https://forum.fhem.de/index.php/topic,105787.msg1013920.html#msg1013920 + $ntime = "23:59:59" if($ntime !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single(Start)?)*?\b/); + + ($year,$mon,$mday) = split "-", $ndate; + $corr = $i != $ele ? 86400 : 0; $t1 = fhemTimeLocal(59, 59, 23, $mday, $mon-1, $year-1900)-$corr; - ($ndate,undef) = split(" ",FmtDateTime($t1)); + ($ndate,undef) = split " ", FmtDateTime($t1); } elsif ($aggr =~ /minute|hour/) { ($hour,$minute) = split ":", $time; if($aggr eq "minute") { - $time = "$hour:$minute:01" if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single)*?\b/); # https://forum.fhem.de/index.php/topic,105787.msg1013920.html#msg1013920 - $ntime = "$hour:$minute:59" if($ntime !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single)*?\b/); + $time = "$hour:$minute:01" if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single(Start)?)*?\b/); # https://forum.fhem.de/index.php/topic,105787.msg1013920.html#msg1013920 + $ntime = "$hour:$minute:59" if($ntime !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single(Start)?)*?\b/); } if($aggr eq "hour") { - $time = "$hour:00:01" if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single)*?\b/); # https://forum.fhem.de/index.php/topic,105787.msg1013920.html#msg1013920 - $ntime = "$hour:59:59" if($ntime !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single)*?\b/); - } - - if ($ntime eq "23:59:59") { - ($year,$mon,$mday) = split("-", $ndate); - $t1 = fhemTimeLocal(59, 59, 23, $mday, $mon-1, $year-1900)-86400; - ($ndate,undef) = split(" ",FmtDateTime($t1)); + $time = "$hour:00:01" if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single(Start)?)*?\b/); # https://forum.fhem.de/index.php/topic,105787.msg1013920.html#msg1013920 + $ntime = "$hour:59:59" if($ntime !~ /^(\d{2}):(\d{2}):(\d{2})$/ || $hash->{LASTCMD} =~ /\bwriteToDB(Single(Start)?)*?\b/); } } @@ -12783,23 +12883,23 @@ sub DbRep_OutputWriteToDB { ($device,$type,$event,$reading,$value,$unit) = DbLog_cutCol($dbloghash,$device,$type,$event,$reading,$value,$unit); if($i == 0) { - push(@row_array, "$date $time|$device|$type|$event|$reading|$value|$unit") if($hash->{LASTCMD} !~ /\bwriteToDBSingle\b/); - push(@row_array, "$ndate $ntime|$device|$type|$event|$reading|$value|$unit"); + push @wr_arr, "$date $time|$device|$type|$event|$reading|$value|$unit" if($hash->{LASTCMD} !~ /\bwriteToDBSingle\b/); + push @wr_arr, "$ndate $ntime|$device|$type|$event|$reading|$value|$unit" if($hash->{LASTCMD} !~ /\bwriteToDBSingleStart\b/); } else { if ($aggr =~ /no|day|week|month|year/) { - ($year,$mon,$mday) = split("-", $date); + ($year,$mon,$mday) = split "-", $date; $t1 = fhemTimeLocal(01, 00, 00, $mday, $mon-1, $year-1900); - ($date,$time) = split(" ",FmtDateTime($t1)); + ($date,$time) = split " ", FmtDateTime($t1); } elsif ($aggr =~ /hour/) { - ($year,$mon,$mday) = split("-", $date); + ($year,$mon,$mday) = split "-", $date; $t1 = fhemTimeLocal(01, 00, $hour, $mday, $mon-1, $year-1900); - ($date,$time) = split(" ",FmtDateTime($t1)); + ($date,$time) = split " ", FmtDateTime($t1); } - push(@row_array, "$date $time|$device|$type|$event|$reading|$value|$unit") if($hash->{LASTCMD} !~ /\bwriteToDBSingle\b/); - push(@row_array, "$ndate $ntime|$device|$type|$event|$reading|$value|$unit"); + push @wr_arr, "$date $time|$device|$type|$event|$reading|$value|$unit" if($hash->{LASTCMD} !~ /\bwriteToDBSingle\b/); + push @wr_arr, "$ndate $ntime|$device|$type|$event|$reading|$value|$unit" if($hash->{LASTCMD} !~ /\bwriteToDBSingleStart\b/); } } $i++; @@ -12807,14 +12907,14 @@ sub DbRep_OutputWriteToDB { } if($optxt =~ /min|max|diff/) { - my %rh = split("§", $wrstr); + my %rh = split "§", $wrstr; for my $key (sort(keys(%rh))) { - my @k = split("\\|",$rh{$key}); - $value = defined($k[1])?sprintf("%.${ndp}f",$k[1]):undef; + my @k = split "\\|", $rh{$key}; + $value = defined($k[1]) ? sprintf("%.${ndp}f",$k[1]) : undef; $rsf = $k[2]; # Datum / Zeit für DB-Speicherung - ($date,$time) = split("_",$rsf); + ($date,$time) = split "_", $rsf; $time =~ s/-/:/g if($time); if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/) { @@ -12830,143 +12930,155 @@ sub DbRep_OutputWriteToDB { } if ($value) { ($device,$type,$event,$reading,$value,$unit) = DbLog_cutCol($dbloghash,$device,$type,$event,$reading,$value,$unit); - push(@row_array, "$date $time|$device|$type|$event|$reading|$value|$unit"); + push @wr_arr, "$date $time|$device|$type|$event|$reading|$value|$unit"; } } } + + return ($err,$wrt,$irowdone) if(!@wr_arr); + + #Log3 ($name, 2, "DbRep $name - data for write: \n". Dumper @wr_arr); + #return; - if (@row_array) { # Schreibzyklus aktivieren - eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, mysql_enable_utf8 => $utf8 });}; - if ($@) { - $err = $@; - - Log3 ($name, 2, "DbRep $name - ERROR - $@"); - - $err = encode_base64($err,""); - - return ($err,$wrt,$irowdone); - } + # Schreibzyklus + ################## + ($err, $dbh, my $dbmodel) = DbRep_dbConnect ($name, 0); + return ($err,$wrt,$irowdone) if ($err); - # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK - my ($usepkh,$usepkc,$pkh,$pkc); + # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK + my ($usepkh,$usepkc,$pkh,$pkc); - if (!$supk) { - ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbloghash,$dbh); - } - else { - Log3 ($hash->{NAME}, 5, "DbRep $name -> Primary Key usage suppressed by attribute noSupportPK in DbLog \"$dblogname\""); - } + if (!$supk) { + ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbloghash,$dbh); + } + else { + Log3 ($name, 5, "DbRep $name -> Primary Key usage suppressed by attribute noSupportPK in DbLog >$dblogname<"); + } + + my $sql = DbRep_createInsertSQLscheme ('history', $dbmodel, $usepkh); + ($err, my $sth_ih) = DbRep_prepareOnly ($name, $dbh, $sql); + return ($err,$wrt,$irowdone) if ($err); + + $sql = DbRep_createUpdateSQLscheme ('history'); + ($err, my $sth_uh) = DbRep_prepareOnly ($name, $dbh, $sql); + return ($err,$wrt,$irowdone) if ($err); + + $sql = DbRep_createInsertSQLscheme ('current', $dbmodel, $usepkc); + ($err, my $sth_ic) = DbRep_prepareOnly ($name, $dbh, $sql); + return ($err,$wrt,$irowdone) if ($err); + + $sql = DbRep_createUpdateSQLscheme ('current'); + ($err, my $sth_uc) = DbRep_prepareOnly ($name, $dbh, $sql); + return ($err,$wrt,$irowdone) if ($err); - if (lc($DbLogType) =~ m(history)) { # INSERT history mit/ohne primary key - if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') { - eval { $sth_ih = $dbh->prepare_cached("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); }; - } - elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') { - eval { $sth_ih = $dbh->prepare_cached("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); }; - } - elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') { - eval { $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); }; - } - else { - eval { $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); }; - } - if ($@) { - $err = $@; - Log3 ($name, 2, "DbRep $name - ERROR - $@"); - $err = encode_base64($err,""); - return ($err,$wrt,$irowdone); - } - } + $err = DbRep_beginDatabaseTransaction ($name, $dbh); + return ($err,$wrt,$irowdone) if ($err); - if (lc($DbLogType) =~ m(current) ) { # INSERT current mit/ohne primary key - if ($usepkc && $hash->{MODEL} eq 'MYSQL') { - eval { $sth_ic = $dbh->prepare("INSERT IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); }; - } - elsif ($usepkc && $hash->{MODEL} eq 'SQLITE') { - eval { $sth_ic = $dbh->prepare("INSERT OR IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); }; - } - elsif ($usepkc && $hash->{MODEL} eq 'POSTGRESQL') { - eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); }; - } - else { - # old behavior - eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); }; - } - if ($@) { - $err = $@; - Log3 ($name, 2, "DbRep $name - ERROR - $@"); - $err = encode_base64($err,""); - return ($err,$wrt,$irowdone); - } - } + my $wst = [gettimeofday]; # SQL-Startzeit - eval { $dbh->begin_work() if($dbh->{AutoCommit}); }; - if ($@) { - Log3($name, 2, "DbRep $name - ERROR - $@"); - } + my $ihs = 0; + my $uhs = 0; - my $wst = [gettimeofday]; # SQL-Startzeit + for my $row (@wr_arr) { + my @a = split "\\|", $row; + + $timestamp = $a[0]; + $device = $a[1]; + $type = $a[2]; + $event = $a[3]; + $reading = $a[4]; + $value = $a[5]; + $unit = $a[6]; - my $ihs = 0; - my $uhs = 0; - - for my $row (@row_array) { - my @a = split("\\|",$row); - $timestamp = $a[0]; - $device = $a[1]; - $type = $a[2]; - $event = $a[3]; - $reading = $a[4]; - $value = $a[5]; - $unit = $a[6]; - - eval { # update oder insert history - if (lc($DbLogType) =~ m(history) ) { - my $rv_uh = $dbh->do("UPDATE history SET TIMESTAMP=\"$timestamp\", DEVICE=\"$device\", READING=\"$reading\", TYPE=\"$type\", EVENT=\"$event\", VALUE=\"$value\", UNIT=\"$unit\" WHERE TIMESTAMP=\"$timestamp\" AND DEVICE=\"$device\" AND READING=\"$reading\""); - $uhs += $rv_uh if($rv_uh); - - Log3 ($hash->{NAME}, 4, "DbRep $name - UPDATE history: $row, RESULT: $rv_uh"); - - if ($rv_uh == 0) { - my $rv_ih = $sth_ih->execute($timestamp,$device,$type,$event,$reading,$value,$unit); - $ihs += $rv_ih if($rv_ih); - Log3 $hash->{NAME}, 4, "DbRep $name - INSERT history: $row, RESULT: $rv_ih"; - } - } - - if (lc($DbLogType) =~ m(current) ) { # update oder insert current - my $rv_uc = $dbh->do("UPDATE current SET TIMESTAMP=\"$timestamp\", DEVICE=\"$device\", READING=\"$reading\", TYPE=\"$type\", EVENT=\"$event\", VALUE=\"$value\", UNIT=\"$unit\" WHERE DEVICE=\"$device\" AND READING=\"$reading\""); - if ($rv_uc == 0) { - $sth_ic->execute($timestamp,$device,$type,$event,$reading,$value,$unit); - } - } - }; - - if ($@) { - $err = $@; - - Log3 ($name, 2, "DbRep $name - ERROR - $@"); - - $dbh->rollback; + if (lc($DbLogType) =~ m(history) ) { + ($err, my $rv_uh) = DbRep_execUpdatePrepared ( { name => $name, + sth => $sth_uh, + timestamp => $timestamp, + device => $device, + type => $type, + event => $event, + reading => $reading, + value => $value, + unit => $unit + } + ); + if ($err) { $dbh->disconnect; + return ($err,$wrt,$irowdone); + } + + $uhs += $rv_uh if($rv_uh); + + Log3 ($name, 4, "DbRep $name - UPDATE history: $row, RESULT: $rv_uh"); + + if ($rv_uh == 0) { + ($err, my $rv_ih) = DbRep_execInsertPrepared ( { name => $name, + sth => $sth_ih, + timestamp => $timestamp, + device => $device, + type => $type, + event => $event, + reading => $reading, + value => $value, + unit => $unit + } + ); + if ($err) { + $dbh->disconnect; + return ($err,$wrt,$irowdone); + } - $err = encode_base64($err,""); + $ihs += $rv_ih if($rv_ih); - return ($err,$wrt,0); + Log3 ($name, 4, "DbRep $name - INSERT history: $row, RESULT: $rv_ih"); + } + } + + if (lc($DbLogType) =~ m(current) ) { + ($err, my $rv_uc) = DbRep_execUpdatePrepared ( { name => $name, + sth => $sth_uc, + timestamp => $timestamp, + device => $device, + type => $type, + event => $event, + reading => $reading, + value => $value, + unit => $unit + } + ); + if ($err) { + $dbh->disconnect; + return ($err,$wrt,$irowdone); + } + + if ($rv_uc == 0) { + ($err, undef) = DbRep_execInsertPrepared ( { name => $name, + sth => $sth_ic, + timestamp => $timestamp, + device => $device, + type => $type, + event => $event, + reading => $reading, + value => $value, + unit => $unit + } + ); } } - - eval {$dbh->commit() if(!$dbh->{AutoCommit});}; - - $dbh->disconnect; - - Log3 ($hash->{NAME}, 3, "DbRep $name - number of lines updated in \"$dblogname\": $uhs"); - Log3 ($hash->{NAME}, 3, "DbRep $name - number of lines inserted into \"$dblogname\": $ihs"); - $irowdone = $ihs + $uhs; - - $wrt = tv_interval($wst); # SQL-Laufzeit ermitteln } + $err = DbRep_commitOnly ($name, $dbh); + return ($err,$wrt,$irowdone) if ($err); + + $dbh->disconnect; + + Log3 ($name, 3, "DbRep $name - number of lines updated in >$dblogname<: $uhs"); + Log3 ($name, 3, "DbRep $name - number of lines inserted into >$dblogname<: $ihs"); + + $irowdone = $ihs + $uhs; + + $wrt = tv_interval($wst); # SQL-Laufzeit ermitteln + return ($err,$wrt,$irowdone); } @@ -13805,61 +13917,64 @@ return;
-
  • averageValue [display | writeToDB | writeToDBSingle | writeToDBInTime] - - calculates an average value of the database field "VALUE" in the time limits - of the possible time.*-attributes.

    +
  • averageValue [display | writeToDB | writeToDBSingle | writeToDBSingleStart | writeToDBInTime]

    + + Calculates an average value of the database field "VALUE" in the time limits + of the possible time.*-attributes.

    - The reading to be evaluated must be specified in the attribute reading - must be specified. - With the attribute averageCalcForm the calculation variant - is used for Averaging defined.

    + The reading to be evaluated must be specified in the attribute reading + must be specified. + With the attribute averageCalcForm the calculation variant + is used for Averaging defined.

    - If none or the option display is specified, the results are only displayed. With - the options writeToDB, writeToDBSingle or writeToDBInTime the calculation results are written - with a new reading name into the database.

    + If none or the option display is specified, the results are only displayed. With + the options writeToDB, writeToDBSingle, writeToDBSingleStart or writeToDBInTime the + calculation results are written with a new reading name into the database. +

    - -
    + +
    - The new reading name is formed from a prefix and the original reading name, - where the original reading name can be replaced by the attribute "readingNameMap". - The prefix consists of the educational function and the aggregation.
    - The timestamp of the new readings in the database is determined by the set aggregation period - if no clear time of the result can be determined. - The field "EVENT" is filled with "calculated".

    + The new reading name is formed from a prefix and the original reading name, + where the original reading name can be replaced by the attribute "readingNameMap". + The prefix consists of the educational function and the aggregation.
    + The timestamp of the new readings in the database is determined by the set aggregation period + if no clear time of the result can be determined. + The field "EVENT" is filled with "calculated".

    - -
    + +
    - Summarized the relevant attributes to control this function are:

    + Summarized the relevant attributes to control this function are:

    - -
    -
    + +
    +
    -
  • +
  • cancelDump - stops a running database dump.

  • @@ -15264,48 +15379,63 @@ return;

    - The relevant attributes to control this function are:

    + The following attributes are relevant for this function:



    -
  • vacuum - optimize tables in the connected database (SQLite, PostgreSQL).
    - Before and after an optimization it is possible to execute a FHEM command. - (please see attributes executeBeforeProc, executeAfterProc) -

    + +
  • vacuum

    + + Optimizes the tables in the connected database (SQLite, PostgreSQL).
    + Especially for SQLite databases it is strongly recommended to temporarily close the connection of the relevant DbLog + device to the database (see DbLog reopen command). +
    +
    + + The following attributes are relevant for this function:

    -
    + +
    +
    -
    - + Hinweis:
    + When the vacuum command is executed, the PRAGMA auto_vacuum = FULL is automatically applied to SQLite databases. +
  • +
    - For all evaluation variants (except sqlCmd,deviceRename,readingRename) applies:
    - In addition to the needed reading the device can be complemented to restrict the datasets for reporting / function. - If no time limit attribute is set but aggregation is set, the period from the oldest dataset in database to the current - date/time will be used as selection criterion. If the oldest dataset wasn't identified, then '1970-01-01 01:00:00' is used - as start date (see get <name> "minTimestamp" also). - If both time limit attribute and aggregation isn't set, the selection on database is runnung without timestamp criterion. -

    +
    + + - Note:
    + For all evaluation variants (except sqlCmd,deviceRename,readingRename) applies:
    + In addition to the needed reading the device can be complemented to restrict the datasets for reporting / function. + If no time limit attribute is set but aggregation is set, the period from the oldest dataset in database to the current + date/time will be used as selection criterion. If the oldest dataset wasn't identified, then '1970-01-01 01:00:00' is used + as start date (see get <name> "minTimestamp" also). + If both time limit attribute and aggregation isn't set, the selection on database is runnung without timestamp criterion. +

    - If you are in detail view it could be necessary to refresh the browser to see the result of operation as soon in DeviceOverview section "state = done" will be shown. + Note:
    -

    + If you are in detail view it could be necessary to refresh the browser to see the result of operation as soon in DeviceOverview section "state = done" will be shown. + +

    @@ -16666,64 +16796,65 @@ return;
    -
  • averageValue [display | writeToDB | writeToDBSingle | writeToDBInTime] - - berechnet einen Durchschnittswert des Datenbankfelds "VALUE" in den Zeitgrenzen - der möglichen time.*-Attribute.

    -
  • +
  • averageValue [display | writeToDB | writeToDBSingle | writeToDBSingleStart | writeToDBInTime]

    + + Berechnet einen Durchschnittswert des Datenbankfelds "VALUE" in den Zeitgrenzen + der möglichen time.*-Attribute.

    - Es muss das auszuwertende Reading im Attribut reading - angegeben sein. - Mit dem Attribut averageCalcForm wird die Berechnungsvariante zur - Mittelwertermittlung definiert.
    - Ist keine oder die Option display angegeben, werden die Ergebnisse nur angezeigt. Mit - den Optionen writeToDB, writeToDBSingle bzw. writeToDBInTime werden die Berechnungsergebnisse - mit einem neuen Readingnamen in der Datenbank gespeichert.

    + Es muss das auszuwertende Reading im Attribut reading + angegeben sein. + Mit dem Attribut averageCalcForm wird die Berechnungsvariante zur + Mittelwertermittlung definiert.
    + Ist keine oder die Option display angegeben, werden die Ergebnisse nur angezeigt. Mit + den Optionen writeToDB, writeToDBSingle, writeToDBSingleStart bzw. writeToDBInTime + werden die Berechnungsergebnisse mit einem neuen Readingnamen in der Datenbank gespeichert.

    - -
    + +
    - Der neue Readingname wird aus einem Präfix und dem originalen Readingnamen gebildet, - wobei der originale Readingname durch das Attribut "readingNameMap" ersetzt werden kann. - Der Präfix setzt sich aus der Bildungsfunktion und der Aggregation zusammen.
    - Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode - abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann. - Das Feld "EVENT" wird mit "calculated" gefüllt.

    + Der neue Readingname wird aus einem Präfix und dem originalen Readingnamen gebildet, + wobei der originale Readingname durch das Attribut "readingNameMap" ersetzt werden kann. + Der Präfix setzt sich aus der Bildungsfunktion und der Aggregation zusammen.
    + Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode + abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann. + Das Feld "EVENT" wird mit "calculated" gefüllt.

    - -
    + +
    - Zusammengefasst sind die zur Steuerung dieser Funktion relevanten Attribute:

    - - -
    -
    - -
    + Zusammengefasst sind die zur Steuerung dieser Funktion relevanten Attribute:

    + +
    +
    + +
  • +
  • cancelDump - bricht einen laufenden Datenbankdump ab.

  • @@ -18169,21 +18300,35 @@ return; -
  • vacuum - optimiert die Tabellen in der angeschlossenen Datenbank (SQLite, PostgreSQL).
    - Vor und nach der Optimierung kann ein FHEM-Kommando ausgeführt werden. - (siehe Attribute executeBeforeProc, - executeAfterProc) -

    +
  • vacuum

    + + Optimiert die Tabellen in der angeschlossenen Datenbank (SQLite, PostgreSQL).
    + Insbesondere für SQLite Datenbanken ist unbedingt empfehlenswert die Verbindung des relevanten DbLog-Devices zur + Datenbank vorübergehend zu schließen (siehe DbLog reopen Kommando). +
    +
    + + Für diese Funktion sind folgende Attribute relevant:

    -
    + +
    +
    -
    - + Hinweis:
    + Bei der Ausführung des vacuum Kommandos wird bei SQLite Datenbanken automatisch das PRAGMA auto_vacuum = FULL + angewendet. +
  • +
    + +
    + + Für alle Auswertungsvarianten (Ausnahme sqlCmd,deviceRename,readingRename) gilt:
    Zusätzlich zu dem auszuwertenden Reading kann das Device mit angegeben werden um das Reporting nach diesen Kriterien einzuschränken.