From dc09d933dcdbdbca29e4b6c9185b933ce188706a Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Mon, 9 Sep 2019 17:29:19 +0000 Subject: [PATCH] 93_DbRep: SQL Wildcard (%) usage possible as placeholder in a reading- or device-list. Forum: #101756, internal code changes of SQL statement generation git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@20138 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- CHANGED | 3 + FHEM/93_DbRep.pm | 631 +++++++++++++++++++++++++++++++---------------- 2 files changed, 424 insertions(+), 210 deletions(-) diff --git a/CHANGED b/CHANGED index e1eb8c848..3632a9a8a 100644 --- a/CHANGED +++ b/CHANGED @@ -1,5 +1,8 @@ # 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: SQL Wildcard (%) usage possible as placeholder in a + reading- or device-list. Forum: #101756, + internal code changes of SQL statement generation - feature: 14_SD_BELL: added model Grothe_Mistral_SE_03 | Grothe_Mistral_SE_01 fixed repeats Heidemann_|_Heidemann_HX_|_VTX-BELL - bugfix: 14_SD_UT: fixed Manax MX-RCS250 | mumbi button ALL diff --git a/FHEM/93_DbRep.pm b/FHEM/93_DbRep.pm index ebb994230..0567b4c87 100644 --- a/FHEM/93_DbRep.pm +++ b/FHEM/93_DbRep.pm @@ -58,6 +58,10 @@ no if $] >= 5.017011, warnings => 'experimental::smartmatch'; # Version History intern our %DbRep_vNotesIntern = ( + "8.26.0" => "07.09.2019 make SQL Wildcard (\%) possible as placeholder in a reading list: https://forum.fhem.de/index.php/topic,101756.0.html ". + "sub DbRep_createUpdateSql deleted, new sub DbRep_createCommonSql ", + "8.25.0" => "01.09.2019 make SQL Wildcard (\%) possible as placeholder in a device list: https://forum.fhem.de/index.php/topic,101756.0.html ". + "sub DbRep_modAssociatedWith changed ", "8.24.0" => "24.08.2019 devices marked as \"Associated With\" if possible, fhem.pl 20069 2019-08-27 08:36:02Z is needed ", "8.23.1" => "26.08.2019 fix add newline at the end of DbRep_dbValue result, Forum: #103295 ", "8.23.0" => "24.08.2019 prepared for devices marked as \"Associated With\" if possible ", @@ -158,6 +162,12 @@ our %DbRep_vNotesIntern = ( # Version History extern: our %DbRep_vNotesExtern = ( + "8.25.0" => "29.08.2019 If a list of devices in attribute \"device\" contains a SQL wildcard (\%), this wildcard is now " + ."dissolved into separate devices if they are still existing in your FHEM configuration. " + ."Please see this Forum Thread " + ."for further information. ", + "8.24.0" => "24.08.2019 Devices which are specified in attribute \"device\" are marked as \"Associated With\" if they are " + ."still existing in your FHEM configuration. At least fhem.pl 20069 2019-08-27 08:36:02 is needed. ", "8.22.0" => "23.08.2019 A new attribute \"fetchValueFn\" is provided. When fetching the database content, you are able to manipulate ". "the value displayed from the VALUE database field before create the appropriate reading. ", "8.21.0" => "28.04.2019 FHEM command \"dbReadingsVal\" implemented.", @@ -2834,7 +2844,7 @@ sub count_DoParse($) { if ($@) { $err = encode_base64($@,""); Log3 ($name, 2, "DbRep $name - $@"); - return "$name|''|$device|$reading|''|$err|$table"; + return "$name|''|$device|''|$err|$table"; } # only for this block because of warnings if details of readings are not set @@ -2889,7 +2899,7 @@ sub count_DoParse($) { $err = encode_base64($@,""); Log3 ($name, 2, "DbRep $name - $@"); $dbh->disconnect; - return "$name|''|$device|$reading|''|$err|$table"; + return "$name|''|$device|''|$err|$table"; } if($ced) { @@ -2971,7 +2981,8 @@ sub count_ParseDone($) { } else { my $ds = $device."__" if ($device); my $rds = $reading."__" if ($reading); - $reading_runtime_string = $rsf.$ds.$rds."COUNT_".$table."__".$runtime_string; + # $reading_runtime_string = $rsf.$ds.$rds."COUNT_".$table."__".$runtime_string; + $reading_runtime_string = $rsf."COUNT_".$table."__".$runtime_string; } ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $c?$c:"-"); @@ -4264,7 +4275,7 @@ sub currentfillup_Push($) { my $dblogname = $dbloghash->{NAME}; my $dbpassword = $attr{"sec$dblogname"}{secret}; my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0; - my ($err,$sth,$sql,$devs,$danz,$ranz); + my ($err,$sth,$sql,$selspec,$addon,@dwc,@rwc); # Background-Startzeit my $bst = [gettimeofday]; @@ -4284,8 +4295,6 @@ sub currentfillup_Push($) { # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef) my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash); Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet"); - - ($devs,$danz,$reading,$ranz) = DbRep_specsForSql($hash,$device,$reading); # SQL-Startzeit my $st = [gettimeofday]; @@ -4293,84 +4302,36 @@ sub currentfillup_Push($) { # insert history mit/ohne primary key # SQL zusammenstellen für DB-Operation if ($usepkc && $dbloghash->{MODEL} eq 'MYSQL') { - $sql = "INSERT IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where "; - $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%)); - $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%)); - $sql .= "DEVICE IN ($devs) AND " if($danz > 1); - $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%)); - $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%)); - $sql .= "READING IN ($reading) AND " if($ranz > 1); - if ($IsTimeSet) { - $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' "; - } else { - $sql .= "1 "; - } - $sql .= "group by timestamp,device,reading ;"; + $selspec = "INSERT IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where"; + $addon = "group by timestamp,device,reading"; } elsif ($usepkc && $dbloghash->{MODEL} eq 'SQLITE') { - $sql = "INSERT OR IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where "; - $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%)); - $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%)); - $sql .= "DEVICE IN ($devs) AND " if($danz > 1); - $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%)); - $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%)); - $sql .= "READING IN ($reading) AND " if($ranz > 1); - if ($IsTimeSet) { - $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' "; - } else { - $sql .= "1 "; - } - $sql .= "group by timestamp,device,reading ;"; + $selspec = "INSERT OR IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where"; + $addon = "group by timestamp,device,reading"; } elsif ($usepkc && $dbloghash->{MODEL} eq 'POSTGRESQL') { - $sql = "INSERT INTO current (DEVICE,TIMESTAMP,READING) SELECT device, (array_agg(timestamp ORDER BY reading ASC))[1], reading FROM history where "; - $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%)); - $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%)); - $sql .= "DEVICE IN ($devs) AND " if($danz > 1); - $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%)); - $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%)); - $sql .= "READING IN ($reading) AND " if($ranz > 1); - if ($IsTimeSet) { - $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' "; - } else { - $sql .= "true "; - } - $sql .= "group by device,reading ON CONFLICT ($pkc) DO NOTHING; "; + $selspec = "INSERT INTO current (DEVICE,TIMESTAMP,READING) SELECT device, (array_agg(timestamp ORDER BY reading ASC))[1], reading FROM history where"; + $addon = "group by device,reading ON CONFLICT ($pkc) DO NOTHING"; } else { if($dbloghash->{MODEL} ne 'POSTGRESQL') { # MySQL und SQLite - $sql = "INSERT INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where "; - $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%)); - $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%)); - $sql .= "DEVICE IN ($devs) AND " if($danz > 1); - $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%)); - $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%)); - $sql .= "READING IN ($reading) AND " if($ranz > 1); - if ($IsTimeSet) { - $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' "; - } else { - $sql .= "1 "; - } - $sql .= "group by device,reading ;"; + $selspec = "INSERT INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where"; + $addon = "group by device,reading"; } else { # PostgreSQL - $sql = "INSERT INTO current (DEVICE,TIMESTAMP,READING) SELECT device, (array_agg(timestamp ORDER BY reading ASC))[1], reading FROM history where "; - $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%)); - $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%)); - $sql .= "DEVICE IN ($devs) AND " if($danz > 1); - $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%)); - $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%)); - $sql .= "READING IN ($reading) AND " if($ranz > 1); - if ($IsTimeSet) { - $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' "; - } else { - $sql .= "true "; - } - $sql .= "group by device,reading;"; + $selspec = "INSERT INTO current (DEVICE,TIMESTAMP,READING) SELECT device, (array_agg(timestamp ORDER BY reading ASC))[1], reading FROM history where"; + $addon = "group by device,reading"; } } + # SQL-Statement zusammenstellen + if ($IsTimeSet || $IsAggrSet) { + $sql = DbRep_createCommonSql($hash,$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addon); + } else { + $sql = DbRep_createCommonSql($hash,$selspec,$device,$reading,undef,undef,$addon); + } + # Log SQL Statement Log3 ($name, 4, "DbRep $name - SQL execute: $sql"); @@ -4610,11 +4571,12 @@ sub changeval_Push($) { $new =~ s/'/''/g; # escape ' with '' # SQL zusammenstellen für DB-Update - my $addon = $old =~ /%/?"WHERE VALUE LIKE '$old'":"WHERE VALUE='$old'"; + my $addon = $old =~ /%/?"WHERE VALUE LIKE '$old'":"WHERE VALUE='$old'"; + my $selspec = "UPDATE $table SET TIMESTAMP=TIMESTAMP,VALUE='$new' $addon AND "; if ($IsTimeSet) { - $sql = DbRep_createUpdateSql($hash,$table,"TIMESTAMP=TIMESTAMP,VALUE='$new' $addon",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",''); + $sql = DbRep_createCommonSql($hash,$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",''); } else { - $sql = DbRep_createUpdateSql($hash,$table,"TIMESTAMP=TIMESTAMP,VALUE='$new' $addon",$device,$reading,undef,undef,''); + $sql = DbRep_createCommonSql($hash,$selspec,$device,$reading,undef,undef,''); } Log3 ($name, 4, "DbRep $name - SQL execute: $sql"); $sth = $dbh->prepare($sql) ; @@ -8329,16 +8291,15 @@ sub DbRep_reduceLog($) { $filter = 1; } - my ($idevs,$idanz,$ireading,$iranz,$edevs,$edanz,$ereading,$eranz) = DbRep_specsForSql($hash,$d,$r); + my ($idevs,$idevswc,$idanz,$ireading,$iranz,$irdswc,$edevs,$edevswc,$edanz,$ereading,$eranz,$erdswc) = DbRep_specsForSql($hash,$d,$r); # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef) my ($IsTimeSet,$IsAggrSet,$aggregation) = DbRep_checktimeaggr($hash); Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet"); my $sql; - my $table = "history"; - my $selspec = "TIMESTAMP,DEVICE,'',READING,VALUE"; - my $addon = "ORDER BY TIMESTAMP ASC"; + my $selspec = "SELECT TIMESTAMP,DEVICE,'',READING,VALUE FROM history where "; + my $addon = "ORDER BY TIMESTAMP ASC"; if($filter) { # Option EX/INCLUDE wurde angegeben @@ -8346,9 +8307,9 @@ sub DbRep_reduceLog($) { .($a[-1] =~ /^INCLUDE=(.+):(.+)$/i ? "DEVICE like '$1' AND READING like '$2' AND " : '') ."TIMESTAMP <= '$ots'".($nts?" AND TIMESTAMP >= '$nts' ":" ")."ORDER BY TIMESTAMP ASC"; } elsif ($IsTimeSet || $IsAggrSet) { - $sql = DbRep_createSelectSql($hash,$table,$selspec,$d,$r,"'$nts'","'$ots'",$addon); + $sql = DbRep_createCommonSql($hash,$selspec,$d,$r,"'$nts'","'$ots'",$addon); } else { - $sql = DbRep_createSelectSql($hash,$table,$selspec,$d,$r,undef,undef,$addon); + $sql = DbRep_createCommonSql($hash,$selspec,$d,$r,undef,undef,$addon); } Log3 ($name, 4, "DbRep $name - SQL execute: $sql"); @@ -8361,7 +8322,10 @@ sub DbRep_reduceLog($) { Log3 ($name, 3, "DbRep $name - reduceLog requested with options: " .(($average) ? "$average" : '') .($filter ? ((($average && $filter) ? ", " : '').(uc((split('=',$a[-1]))[0]).'='.(split('=',$a[-1]))[1])) : - ((($idanz || $iranz) ? " INCLUDE -> " : '').($idanz ? "Devs: ".$idevs : '').($iranz ? " Readings: ".$ireading : '').(($edanz || $eranz) ? " EXCLUDE -> " : '').($edanz ? "Devs: ".$edevs : '').($eranz ? " Readings: ".$ereading : '')) )); + ((($idanz || $idevswc || $iranz || $irdswc) ? " INCLUDE -> " : ''). + (($idanz || $idevswc)? "Devs: ".($idevs?$idevs:'').($idevswc?$idevswc:'') : '').(($iranz || $irdswc) ? " Readings: ".($ireading?$ireading:'').($irdswc?$irdswc:'') : ''). + (($edanz || $edevswc || $eranz || $erdswc) ? " EXCLUDE -> " : ''). + (($edanz || $edevswc)? "Devs: ".($edevs?$edevs:'').($edevswc?$edevswc:'') : '').(($eranz || $erdswc) ? " Readings: ".($ereading?$ereading:'').($erdswc?$erdswc:'') : '')) )); if ($ots) { my ($sth_del, $sth_upd, $sth_delD, $sth_updD, $sth_get); @@ -8854,17 +8818,17 @@ return; } #################################################################################################### -# SQL-Statement zusammenstellen für DB-Abfrage +# SQL-Statement zusammenstellen Common #################################################################################################### -sub DbRep_createSelectSql($$$$$$$$) { - my ($hash,$table,$selspec,$device,$reading,$tf,$tn,$addon) = @_; +sub DbRep_createCommonSql ($$$$$$$) { + my ($hash,$selspec,$device,$reading,$tf,$tn,$addon) = @_; my $name = $hash->{NAME}; my $dbmodel = $hash->{dbloghash}{MODEL}; my $valfilter = AttrVal($name, "valueFilter", undef); # Wertefilter - my ($sql,$vf); - my $tnfull = 0; + my $tnfull = 0; + my ($sql,$vf,@dwc,@rwc); - my ($idevs,$idanz,$ireading,$iranz,$edevs,$edanz,$ereading,$eranz) = DbRep_specsForSql($hash,$device,$reading); + my ($idevs,$idevswc,$idanz,$ireading,$iranz,$irdswc,$edevs,$edevswc,$edanz,$ereading,$eranz,$erdswc) = DbRep_specsForSql($hash,$device,$reading); if($tn && $tn =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) { $tnfull = 1; @@ -8878,23 +8842,69 @@ sub DbRep_createSelectSql($$$$$$$$) { } } - $sql = "SELECT $selspec FROM $table where "; + $sql = $selspec." "; # included devices - $sql .= "DEVICE LIKE '$idevs' AND " if($idanz <= 1 && $idevs !~ m(^%$) && $idevs =~ m(\%)); - $sql .= "DEVICE = '$idevs' AND " if($idanz <= 1 && $idevs !~ m(\%)); - $sql .= "DEVICE IN ($idevs) AND " if($idanz > 1); + $sql .= "( " if(($idanz || $idevswc) && $idevs !~ m(^%$)); + if($idevswc && $idevs !~ m(^%$)) { + @dwc = split(",",$idevswc); + my $i = 1; + my $len = scalar(@dwc); + foreach(@dwc) { + if($i<$len) { + $sql .= "DEVICE LIKE '$_' OR "; + } else { + $sql .= "DEVICE LIKE '$_' "; + } + $i++; + } + if($idanz) { + $sql .= "OR "; + } + } + $sql .= "DEVICE = '$idevs' " if($idanz == 1 && $idevs && $idevs !~ m(^%$)); + $sql .= "DEVICE IN ($idevs) " if($idanz > 1); + $sql .= ") AND " if(($idanz || $idevswc) && $idevs !~ m(^%$)); # excluded devices - $sql .= "DEVICE NOT LIKE '$edevs' AND " if($edanz && $edanz == 1 && $edevs =~ m(\%)); - $sql .= "DEVICE != '$edevs' AND " if($edanz && $edanz == 1 && $edevs !~ m(\%)); - $sql .= "DEVICE NOT IN ($edevs) AND " if($edanz > 1); + if($edevswc) { + @dwc = split(",",$edevswc); + foreach(@dwc) { + $sql .= "DEVICE NOT LIKE '$_' AND "; + } + } + $sql .= "DEVICE != '$edevs' " if($edanz == 1 && $edanz && $edevs !~ m(^%$)); + $sql .= "DEVICE NOT IN ($edevs) " if($edanz > 1); + $sql .= "AND " if($edanz && $edevs !~ m(^%$)); # included readings - $sql .= "READING LIKE '$ireading' AND " if($iranz <= 1 && $ireading !~ m(^%$) && $ireading =~ m(\%)); - $sql .= "READING = '$ireading' AND " if($iranz <= 1 && $ireading !~ m(\%)); - $sql .= "READING IN ($ireading) AND " if($iranz > 1); + $sql .= "( " if(($iranz || $irdswc) && $ireading !~ m(^%$)); + if($irdswc && $ireading !~ m(^%$)) { + @rwc = split(",",$irdswc); + my $i = 1; + my $len = scalar(@rwc); + foreach(@rwc) { + if($i<$len) { + $sql .= "READING LIKE '$_' OR "; + } else { + $sql .= "READING LIKE '$_' "; + } + $i++; + } + if($iranz) { + $sql .= "OR "; + } + } + $sql .= "READING = '$ireading' " if($iranz == 1 && $ireading && $ireading !~ m(\%)); + $sql .= "READING IN ($ireading) " if($iranz > 1); + $sql .= ") AND " if(($iranz || $irdswc) && $ireading !~ m(^%$)); # excluded readings - $sql .= "READING NOT LIKE '$ereading' AND " if($eranz && $eranz == 1 && $ereading =~ m(\%)); - $sql .= "READING != '$ereading' AND " if($eranz && $eranz == 1 && $ereading !~ m(\%)); - $sql .= "READING NOT IN ($ereading) AND " if($eranz > 1); + if($erdswc) { + @dwc = split(",",$erdswc); + foreach(@dwc) { + $sql .= "READING NOT LIKE '$_' AND "; + } + } + $sql .= "READING != '$ereading' " if($eranz && $eranz == 1 && $ereading !~ m(\%)); + $sql .= "READING NOT IN ($ereading) " if($eranz > 1); + $sql .= "AND " if($eranz && $ereading !~ m(^%$)); # add valueFilter $sql .= $vf if(defined $vf); # Timestamp Filter @@ -8913,19 +8923,19 @@ return $sql; } #################################################################################################### -# SQL-Statement zusammenstellen für DB-Updates +# SQL-Statement zusammenstellen für DB-Abfrage Select #################################################################################################### -sub DbRep_createUpdateSql($$$$$$$$) { +sub DbRep_createSelectSql($$$$$$$$) { my ($hash,$table,$selspec,$device,$reading,$tf,$tn,$addon) = @_; - my $name = $hash->{NAME}; - my $dbmodel = $hash->{dbloghash}{MODEL}; + my $name = $hash->{NAME}; + my $dbmodel = $hash->{dbloghash}{MODEL}; my $valfilter = AttrVal($name, "valueFilter", undef); # Wertefilter - my ($sql,$vf); - my $tnfull = 0; + my $tnfull = 0; + my ($sql,$vf,@dwc,@rwc); - my ($idevs,$idanz,$ireading,$iranz,$edevs,$edanz,$ereading,$eranz) = DbRep_specsForSql($hash,$device,$reading); + my ($idevs,$idevswc,$idanz,$ireading,$iranz,$irdswc,$edevs,$edevswc,$edanz,$ereading,$eranz,$erdswc) = DbRep_specsForSql($hash,$device,$reading); - if($tn =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) { + if($tn && $tn =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) { $tnfull = 1; } @@ -8936,24 +8946,70 @@ sub DbRep_createUpdateSql($$$$$$$$) { $vf = "VALUE REGEXP '$valfilter' AND "; } } - - $sql = "UPDATE $table SET $selspec AND "; + + $sql = "SELECT $selspec FROM $table where "; # included devices - $sql .= "DEVICE LIKE '$idevs' AND " if($idanz <= 1 && $idevs !~ m(^%$) && $idevs =~ m(\%)); - $sql .= "DEVICE = '$idevs' AND " if($idanz <= 1 && $idevs !~ m(\%)); - $sql .= "DEVICE IN ($idevs) AND " if($idanz > 1); + $sql .= "( " if(($idanz || $idevswc) && $idevs !~ m(^%$)); + if($idevswc && $idevs !~ m(^%$)) { + @dwc = split(",",$idevswc); + my $i = 1; + my $len = scalar(@dwc); + foreach(@dwc) { + if($i<$len) { + $sql .= "DEVICE LIKE '$_' OR "; + } else { + $sql .= "DEVICE LIKE '$_' "; + } + $i++; + } + if($idanz) { + $sql .= "OR "; + } + } + $sql .= "DEVICE = '$idevs' " if($idanz == 1 && $idevs && $idevs !~ m(^%$)); + $sql .= "DEVICE IN ($idevs) " if($idanz > 1); + $sql .= ") AND " if(($idanz || $idevswc) && $idevs !~ m(^%$)); # excluded devices - $sql .= "DEVICE NOT LIKE '$edevs' AND " if($edanz && $edanz == 1 && $edevs =~ m(\%)); - $sql .= "DEVICE != '$edevs' AND " if($edanz && $edanz == 1 && $edevs !~ m(\%)); - $sql .= "DEVICE NOT IN ($edevs) AND " if($edanz > 1); + if($edevswc) { + @dwc = split(",",$edevswc); + foreach(@dwc) { + $sql .= "DEVICE NOT LIKE '$_' AND "; + } + } + $sql .= "DEVICE != '$edevs' " if($edanz == 1 && $edanz && $edevs !~ m(^%$)); + $sql .= "DEVICE NOT IN ($edevs) " if($edanz > 1); + $sql .= "AND " if($edanz && $edevs !~ m(^%$)); # included readings - $sql .= "READING LIKE '$ireading' AND " if($iranz <= 1 && $ireading !~ m(^%$) && $ireading =~ m(\%)); - $sql .= "READING = '$ireading' AND " if($iranz <= 1 && $ireading !~ m(\%)); - $sql .= "READING IN ($ireading) AND " if($iranz > 1); + $sql .= "( " if(($iranz || $irdswc) && $ireading !~ m(^%$)); + if($irdswc && $ireading !~ m(^%$)) { + @rwc = split(",",$irdswc); + my $i = 1; + my $len = scalar(@rwc); + foreach(@rwc) { + if($i<$len) { + $sql .= "READING LIKE '$_' OR "; + } else { + $sql .= "READING LIKE '$_' "; + } + $i++; + } + if($iranz) { + $sql .= "OR "; + } + } + $sql .= "READING = '$ireading' " if($iranz == 1 && $ireading && $ireading !~ m(\%)); + $sql .= "READING IN ($ireading) " if($iranz > 1); + $sql .= ") AND " if(($iranz || $irdswc) && $ireading !~ m(^%$)); # excluded readings - $sql .= "READING NOT LIKE '$ereading' AND " if($eranz && $eranz == 1 && $ereading =~ m(\%)); - $sql .= "READING != '$ereading' AND " if($eranz && $eranz == 1 && $ereading !~ m(\%)); - $sql .= "READING NOT IN ($ereading) AND " if($eranz > 1); + if($erdswc) { + @dwc = split(",",$erdswc); + foreach(@dwc) { + $sql .= "READING NOT LIKE '$_' AND "; + } + } + $sql .= "READING != '$ereading' " if($eranz && $eranz == 1 && $ereading !~ m(\%)); + $sql .= "READING NOT IN ($ereading) " if($eranz > 1); + $sql .= "AND " if($eranz && $ereading !~ m(^%$)); # add valueFilter $sql .= $vf if(defined $vf); # Timestamp Filter @@ -8963,7 +9019,7 @@ sub DbRep_createUpdateSql($$$$$$$$) { if ($dbmodel eq "POSTGRESQL") { $sql .= "true "; } else { - $sql .= "1 "; + $sql .= "1 "; } } $sql .= "$addon;"; @@ -8976,18 +9032,18 @@ return $sql; #################################################################################################### sub DbRep_createDeleteSql($$$$$$$) { my ($hash,$table,$device,$reading,$tf,$tn,$addon) = @_; - my $name = $hash->{NAME}; - my $dbmodel = $hash->{dbloghash}{MODEL}; + my $name = $hash->{NAME}; + my $dbmodel = $hash->{dbloghash}{MODEL}; my $valfilter = AttrVal($name, "valueFilter", undef); # Wertefilter - my ($sql,$vf); - my $tnfull = 0; + my $tnfull = 0; + my ($sql,$vf,@dwc,@rwc); if($table eq "current") { $sql = "delete FROM $table; "; return $sql; } - my ($idevs,$idanz,$ireading,$iranz,$edevs,$edanz,$ereading,$eranz) = DbRep_specsForSql($hash,$device,$reading); + my ($idevs,$idevswc,$idanz,$ireading,$iranz,$irdswc,$edevs,$edevswc,$edanz,$ereading,$eranz,$erdswc) = DbRep_specsForSql($hash,$device,$reading); if($tn =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) { $tnfull = 1; @@ -9003,21 +9059,67 @@ sub DbRep_createDeleteSql($$$$$$$) { $sql = "delete FROM $table where "; # included devices - $sql .= "DEVICE LIKE '$idevs' AND " if($idanz <= 1 && $idevs !~ m(^%$) && $idevs =~ m(\%)); - $sql .= "DEVICE = '$idevs' AND " if($idanz <= 1 && $idevs !~ m(\%)); - $sql .= "DEVICE IN ($idevs) AND " if($idanz > 1); + $sql .= "( " if(($idanz || $idevswc) && $idevs !~ m(^%$)); + if($idevswc && $idevs !~ m(^%$)) { + @dwc = split(",",$idevswc); + my $i = 1; + my $len = scalar(@dwc); + foreach(@dwc) { + if($i<$len) { + $sql .= "DEVICE LIKE '$_' OR "; + } else { + $sql .= "DEVICE LIKE '$_' "; + } + $i++; + } + if($idanz) { + $sql .= "OR "; + } + } + $sql .= "DEVICE = '$idevs' " if($idanz == 1 && $idevs && $idevs !~ m(^%$)); + $sql .= "DEVICE IN ($idevs) " if($idanz > 1); + $sql .= ") AND " if(($idanz || $idevswc) && $idevs !~ m(^%$)); # excluded devices - $sql .= "DEVICE NOT LIKE '$edevs' AND " if($edanz && $edanz == 1 && $edevs =~ m(\%)); - $sql .= "DEVICE != '$edevs' AND " if($edanz && $edanz == 1 && $edevs !~ m(\%)); - $sql .= "DEVICE NOT IN ($edevs) AND " if($edanz > 1); + if($edevswc) { + @dwc = split(",",$edevswc); + foreach(@dwc) { + $sql .= "DEVICE NOT LIKE '$_' AND "; + } + } + $sql .= "DEVICE != '$edevs' " if($edanz == 1 && $edanz && $edevs !~ m(^%$)); + $sql .= "DEVICE NOT IN ($edevs) " if($edanz > 1); + $sql .= "AND " if($edanz && $edevs !~ m(^%$)); # included readings - $sql .= "READING LIKE '$ireading' AND " if($iranz <= 1 && $ireading !~ m(^%$) && $ireading =~ m(\%)); - $sql .= "READING = '$ireading' AND " if($iranz <= 1 && $ireading !~ m(\%)); - $sql .= "READING IN ($ireading) AND " if($iranz > 1); + $sql .= "( " if(($iranz || $irdswc) && $ireading !~ m(^%$)); + if($irdswc && $ireading !~ m(^%$)) { + @rwc = split(",",$irdswc); + my $i = 1; + my $len = scalar(@rwc); + foreach(@rwc) { + if($i<$len) { + $sql .= "READING LIKE '$_' OR "; + } else { + $sql .= "READING LIKE '$_' "; + } + $i++; + } + if($iranz) { + $sql .= "OR "; + } + } + $sql .= "READING = '$ireading' " if($iranz == 1 && $ireading && $ireading !~ m(\%)); + $sql .= "READING IN ($ireading) " if($iranz > 1); + $sql .= ") AND " if(($iranz || $irdswc) && $ireading !~ m(^%$)); # excluded readings - $sql .= "READING NOT LIKE '$ereading' AND " if($eranz && $eranz == 1 && $ereading =~ m(\%)); - $sql .= "READING != '$ereading' AND " if($eranz && $eranz == 1 && $ereading !~ m(\%)); - $sql .= "READING NOT IN ($ereading) AND " if($eranz > 1); + if($erdswc) { + @dwc = split(",",$erdswc); + foreach(@dwc) { + $sql .= "READING NOT LIKE '$_' AND "; + } + } + $sql .= "READING != '$ereading' " if($eranz && $eranz == 1 && $ereading !~ m(\%)); + $sql .= "READING NOT IN ($ereading) " if($eranz > 1); + $sql .= "AND " if($eranz && $ereading !~ m(^%$)); # add valueFilter $sql .= $vf if(defined $vf); # Timestamp Filter @@ -9040,11 +9142,13 @@ return $sql; sub DbRep_specsForSql($$$) { my ($hash,$device,$reading) = @_; my $name = $hash->{NAME}; + my (@idvspcs,@edvspcs,@idvs,@edvs,@idvswc,@edvswc,@residevs,@residevswc); + my ($nl,$nlwc) = ("",""); ##### inkludierte / excludierte Devices und deren Anzahl ermitteln ##### - my ($idevice,$edevice) = ('',''); - my ($idevs,$edevs) = ('',''); - my ($idanz,$edanz) = (0,0); + my ($idevice,$edevice) = ('',''); + my ($idevs,$idevswc,$edevs,$edevswc) = ('','','',''); + my ($idanz,$edanz) = (0,0); if($device =~ /EXCLUDE=/i) { ($idevice,$edevice) = split(/EXCLUDE=/i,$device); $idevice = $idevice?DbRep_trim($idevice):"%"; @@ -9052,35 +9156,102 @@ sub DbRep_specsForSql($$$) { $idevice = $device; } - # Devices inkludiert - my @idvspcs = devspec2array($idevice); - $idevs = join(",",@idvspcs); - $idevs =~ s/'/''/g; # escape ' with '' + # Devices exkludiert + if($edevice) { + @edvs = split(",",$edevice); + foreach my $e (@edvs) { + $e =~ s/%/\.*/g if($e !~ /^%$/); # SQL Wildcard % auflösen + @edvspcs = devspec2array($e); + @edvspcs = map {s/\.\*/%/g; $_; } @edvspcs; + if((map {$_ =~ /%/;} @edvspcs) && $edevice !~ /^%$/) { # Devices mit Wildcard (%) erfassen, die nicht aufgelöst werden konnten + $edevswc .= "," if($edevswc); + $edevswc .= join(",",@edvspcs); + } else { + $edevs .= "," if($edevs); + $edevs .= join(",",@edvspcs); + } + } + } + $edanz = split(",",$edevs); # Anzahl der exkludierten Elemente (Lauf1) - $idanz = $#idvspcs+1; + # Devices inkludiert + @idvs = split(",",$idevice); + foreach my $d (@idvs) { + $d =~ s/%/\.*/g if($d !~ /^%$/); # SQL Wildcard % auflösen + @idvspcs = devspec2array($d); + @idvspcs = map {s/\.\*/%/g; $_; } @idvspcs; + if((map {$_ =~ /%/;} @idvspcs) && $idevice !~ /^%$/) { # Devices mit Wildcard (%) erfassen, die nicht aufgelöst werden konnten + $idevswc .= "," if($idevswc); + $idevswc .= join(",",@idvspcs); + } else { + $idevs .= "," if($idevs); + $idevs .= join(",",@idvspcs); + } + } + $idanz = split(",",$idevs); # Anzahl der inkludierten Elemente (Lauf1) + + Log3 $name, 5, "DbRep $name - Devices for operation - \n" + ."included ($idanz): $idevs \n" + ."included with wildcard: $idevswc \n" + ."excluded ($edanz): $edevs \n" + ."excluded with wildcard: $edevswc"; + + # exkludierte Devices aus inkludierten entfernen (aufgelöste) + @idvs = split(",",$idevs); + @edvs = split(",",$edevs); + foreach my $in (@idvs) { + my $inc = 1; + foreach(@edvs) { + next if($in ne $_); + $inc = 0; + $nl .= "|" if($nl); + $nl .= $_; # Liste der entfernten devices füllen + } + push(@residevs, $in) if($inc); + } + $edevs = join(",", map {($_ !~ /$nl/)?$_:();} @edvs) if($nl); + + # exkludierte Devices aus inkludierten entfernen (wildcard konnte nicht aufgelöst werden) + @idvswc = split(",",$idevswc); + @edvswc = split(",",$edevswc); + foreach my $inwc (@idvswc) { + my $inc = 1; + foreach(@edvswc) { + next if($inwc ne $_); + $inc = 0; + $nlwc .= "|" if($nlwc); + $nlwc .= $_; # Liste der entfernten devices füllen + } + push(@residevswc, $inwc) if($inc); + } + $edevswc = join(",", map {($_ !~ /$nlwc/)?$_:();} @edvswc) if($nlwc); + # Log3($name, 1, "DbRep $name - nlwc: $nlwc, mwc: @mwc"); + + # Ergebnis zusammenfassen + $idevs = join(",",@residevs); + $idevs =~ s/'/''/g; # escape ' with '' + $idevswc = join(",",@residevswc); + $idevswc =~ s/'/''/g; # escape ' with '' + + $idanz = split(",",$idevs); # Anzahl der inkludierten Elemente (Lauf2) if($idanz > 1) { $idevs =~ s/,/','/g; $idevs = "'".$idevs."'"; } - # Devices exkludiert - if($edevice) { - my @edvspcs = devspec2array($edevice); - $edevs = join(",",@edvspcs); - $edevs =~ s/'/''/g; # escape ' with '' - - $edanz = $#edvspcs+1; - if($edanz > 1) { - $edevs =~ s/,/','/g; - $edevs = "'".$edevs."'"; - } + $edanz = split(",",$edevs); # Anzahl der exkludierten Elemente (Lauf2) + if($edanz > 1) { + $edevs =~ s/,/','/g; + $edevs = "'".$edevs."'"; } - Log3 $name, 5, "DbRep $name - Devices for operation - included: $idevs , excluded: $edevs"; ##### inkludierte / excludierte Readings und deren Anzahl ermitteln ##### - my ($ireading,$ereading) = ('',''); - my ($iranz,$eranz) = (0,0); + my ($ireading,$ereading) = ('',''); + my ($iranz,$eranz) = (0,0); + my ($erdswc,$erdgs,$irdswc,$irdgs) = ('','','',''); + my (@erds,@irds); + $reading =~ s/'/''/g; # escape ' with '' if($reading =~ /EXCLUDE=/i) { @@ -9090,27 +9261,54 @@ sub DbRep_specsForSql($$$) { $ireading = $reading; } + if($ereading) { + @erds = split(",",$ereading); + foreach my $e (@erds) { + if($e =~ /%/ && $e !~ /^%$/) { # Readings mit Wildcard (%) erfassen + $erdswc .= "," if($erdswc); + $erdswc .= $e; + } else { + $erdgs .= "," if($erdgs); + $erdgs .= $e; + } + } + } + # Readings inkludiert - my @ireads = split(",",$ireading); - $iranz = $#ireads+1; + @irds = split(",",$ireading); + foreach my $i (@irds) { + if($i =~ /%/ && $i !~ /^%$/) { # Readings mit Wildcard (%) erfassen + $irdswc .= "," if($irdswc); + $irdswc .= $i; + } else { + $irdgs .= "," if($irdgs); + $irdgs .= $i; + } + } + + + $iranz = split(",",$irdgs); if($iranz > 1) { - $ireading =~ s/,/','/g; - $ireading = "'".$ireading."'"; + $irdgs =~ s/,/','/g; + $irdgs = "'".$irdgs."'"; } if($ereading) { # Readings exkludiert - my @ereads = split(",",$ereading); - $eranz = $#ereads+1; + $eranz = split(",",$erdgs); if($eranz > 1) { - $ereading =~ s/,/','/g; - $ereading = "'".$ereading."'"; + $erdgs =~ s/,/','/g; + $erdgs = "'".$erdgs."'"; } } - Log3 $name, 5, "DbRep $name - Readings for operation - included: $ireading, excluded: $ereading "; + Log3 $name, 5, "DbRep $name - Readings for operation - \n" + ."included ($iranz): $irdgs \n" + ."included with wildcard: $irdswc \n" + ."excluded ($eranz): $erdgs \n" + ."excluded with wildcard: $erdswc"; -return ($idevs,$idanz,$ireading,$iranz,$edevs,$edanz,$ereading,$eranz); +return ($idevs,$idevswc,$idanz,$irdgs,$iranz,$irdswc,$edevs,$edevswc,$edanz,$erdgs,$eranz,$erdswc); } #################################################################################################### @@ -10796,32 +10994,48 @@ return; ################################################################################### sub DbRep_modAssociatedWith ($$$) { my ($hash,$cmd,$awdev) = @_; - my @naw; - - # my @def = split("{",$hash->{DEF}); - # $hash->{DEF} = $def[0]; + my $name = $hash->{NAME}; + my (@naw,@edvs,@edvspcs,$edevswc); + my ($edevs,$idevice,$edevice) = ('','',''); if($cmd eq "del") { readingsDelete($hash,".associatedWith"); return; } - - my @nadev = split("[, ]", $awdev); + + ($idevice,$edevice) = split(/EXCLUDE=/i,$awdev); + + if($edevice) { + @edvs = split(",",$edevice); + foreach my $e (@edvs) { + $e =~ s/%/\.*/g if($e !~ /^%$/); # SQL Wildcard % auflösen + @edvspcs = devspec2array($e); + @edvspcs = map {s/\.\*/%/g; $_; } @edvspcs; + if((map {$_ =~ /%/;} @edvspcs) && $edevice !~ /^%$/) { # Devices mit Wildcard (%) aussortieren, die nicht aufgelöst werden konnten + $edevswc .= "|" if($edevswc); + $edevswc .= join(" ",@edvspcs); + } else { + $edevs .= "|" if($edevs); + $edevs .= join("|",@edvspcs); + } + } + } + + my @nadev = split("[, ]", $idevice); foreach my $d (@nadev) { - if($defs{$d}) { - push(@naw, $d); - next; - } + $d =~ s/%/\.*/g if($d !~ /^%$/); # SQL Wildcard % in Regex my @a = devspec2array($d); foreach(@a) { next if(!$defs{$_}); - push(@naw, $_) if( !( "EXCLUDE=".$_ ~~ @nadev) ); + push(@naw, $_) if($_ !~ /$edevs/); } } if(@naw) { ReadingsSingleUpdateValue ($hash, ".associatedWith", join(" ",@naw), 0); + } else { + readingsDelete($hash, ".associatedWith"); } return; @@ -11982,12 +12196,12 @@ return;
- For compatibility reasons the reducelog command can optionally be completed with supplements "EXCLUDE" + For compatibility reason the reducelog command can optionally be completed with supplements "EXCLUDE" respectively "INCLUDE" to exclude and/or include device/reading combinations from reducelog:

- This declaration is evaluated as Regex and overwrites the attributes "device" and "reading", + This declaration is evaluated as Regex and overwrites the settings made by attributes "device" and "reading", which won't be considered in this case.