Skip to content

Commit

Permalink
Initial experiments using the new Database query logic with Condition…
Browse files Browse the repository at this point in the history
…al Statistical Functions (#1880)

- Refactoring of the Statistical Conditional functions (`AVERAGEIF()`, `AVERAGEIFS()`, `COUNTIF()`, `COUNTIFS()`, `MAXIFS()` and `MINIFS()` to use the new Database functions codebase.
- Extended unit testing
- Fix handling for null values
- Fixes to wildcard text searches

There's still scope for further improvements to memory usage and performance; but for now the code is stable with all unit tests passing
  • Loading branch information
Mark Baker committed Feb 27, 2021
1 parent 8dcdf58 commit 08673b5
Show file tree
Hide file tree
Showing 22 changed files with 567 additions and 251 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org).

### Added

- Implementation of the Excel `AVERAGEIFS()` functions as part of a restructuring of Database functions and Conditional Statistical functions.
- Support for date values and percentages in query parameters for Database functions, and the IF expressions in functions like COUNTIF() and AVERAGEIF(). [#1875](https://github.com/PHPOffice/PhpSpreadsheet/pull/1875)
- Support for booleans, and for wildcard text search in query parameters for Database functions. [#1876](https://github.com/PHPOffice/PhpSpreadsheet/pull/1876)
- Support for booleans, and for wildcard text search in query parameters for Database functions, and the IF expressions in functions like COUNTIF() and AVERAGEIF(). [#1876](https://github.com/PHPOffice/PhpSpreadsheet/pull/1876)
- Implemented DataBar for conditional formatting in Xlsx, providing read/write and creation of (type, value, direction, fills, border, axis position, color settings) as DataBar options in Excel. [#1754](https://github.com/PHPOffice/PhpSpreadsheet/pull/1754)
- Alignment for ODS Writer [#1796](https://github.com/PHPOffice/PhpSpreadsheet/issues/1796)
- Basic implementation of the PERMUTATIONA() Statistical Function
Expand Down
12 changes: 6 additions & 6 deletions src/PhpSpreadsheet/Calculation/Calculation.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,12 @@ class Calculation
],
'AVERAGEIF' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'AVERAGEIF'],
'functionCall' => [Statistical\Conditional::class, 'AVERAGEIF'],
'argumentCount' => '2,3',
],
'AVERAGEIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'functionCall' => [Statistical\Conditional::class, 'AVERAGEIFS'],
'argumentCount' => '3+',
],
'BAHTTEXT' => [
Expand Down Expand Up @@ -639,12 +639,12 @@ class Calculation
],
'COUNTIF' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'COUNTIF'],
'functionCall' => [Statistical\Conditional::class, 'COUNTIF'],
'argumentCount' => '2',
],
'COUNTIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'COUNTIFS'],
'functionCall' => [Statistical\Conditional::class, 'COUNTIFS'],
'argumentCount' => '2+',
],
'COUPDAYBS' => [
Expand Down Expand Up @@ -1630,7 +1630,7 @@ class Calculation
],
'MAXIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'MAXIFS'],
'functionCall' => [Statistical\Conditional::class, 'MAXIFS'],
'argumentCount' => '3+',
],
'MDETERM' => [
Expand Down Expand Up @@ -1675,7 +1675,7 @@ class Calculation
],
'MINIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'MINIFS'],
'functionCall' => [Statistical\Conditional::class, 'MINIFS'],
'argumentCount' => '3+',
],
'MINUTE' => [
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Calculation/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Database
* the column label in which you specify a condition for the
* column.
*
* @return float|string
* @return null|float|string
*/
public static function DAVERAGE($database, $field, $criteria)
{
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Calculation/Database/DAverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class DAverage extends DatabaseAbstract
* the column label in which you specify a condition for the
* column.
*
* @return float|string
* @return null|float|string
*/
public static function evaluate($database, $field, $criteria)
{
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Calculation/Database/DCountA.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static function evaluate($database, $field, $criteria)
$field = self::fieldExtract($database, $field);

return Statistical::COUNTA(
self::getFilteredColumn($database, $field, $criteria)
self::getFilteredColumn($database, $field ?? 0, $criteria)
);
}
}
4 changes: 3 additions & 1 deletion src/PhpSpreadsheet/Calculation/Database/DGet.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public static function evaluate($database, $field, $criteria)
return Functions::NAN();
}

return $columnData[0];
$row = array_pop($columnData);

return array_pop($row);
}
}
26 changes: 16 additions & 10 deletions src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@ abstract public static function evaluate($database, $field, $criteria);
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
*/
protected static function fieldExtract(array $database, $field): ?string
protected static function fieldExtract(array $database, $field): ?int
{
$field = strtoupper(Functions::flattenSingleValue($field));
$fieldNames = array_map('strtoupper', array_shift($database));
if ($field === '') {
return null;
}

$fieldNames = array_map('strtoupper', array_shift($database));
if (is_numeric($field)) {
$keys = array_keys($fieldNames);

return $keys[$field - 1];
return ((int) $field) - 1;
}
$key = array_search($field, $fieldNames);
$key = array_search($field, array_values($fieldNames), true);

return $key ?: null;
return ($key !== false) ? (int) $key : null;
}

/**
Expand Down Expand Up @@ -70,14 +71,19 @@ protected static function filter(array $database, array $criteria): array
return self::executeQuery($database, $query, $criteriaNames, $fieldNames);
}

protected static function getFilteredColumn(array $database, $field, array $criteria): array
protected static function getFilteredColumn(array $database, ?int $field, array $criteria): array
{
// reduce the database to a set of rows that match all the criteria
$database = self::filter($database, $criteria);
$defaultReturnColumnValue = ($field === null) ? 1 : null;

// extract an array of values for the requested column
$columnData = [];
foreach ($database as $row) {
$columnData[] = ($field !== null) ? $row[$field] : true;
foreach ($database as $rowKey => $row) {
$keys = array_keys($row);
$key = $keys[$field] ?? null;
$columnKey = $key ?? 'A';
$columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue;
}

return $columnData;
Expand Down
11 changes: 8 additions & 3 deletions src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
class WildcardMatch
{
private const SEARCH_SET = [
'/([^~])(\*)/ui',
'/(?<!~)\*/ui',
'/~\*/ui',
'/([^~])(\?)/ui',
'/(?<!~)\?/ui',
'/~\?/ui',
];

Expand All @@ -20,6 +20,11 @@ class WildcardMatch

public static function wildcard(string $wildcard): string
{
// Preg Escape the wildcard, but protecting the Excel * and ? search characters
$wildcard = str_replace(['*', '?'], [0x1A, 0x1B], $wildcard);
$wildcard = preg_quote($wildcard);
$wildcard = str_replace([0x1A, 0x1B], ['*', '?'], $wildcard);

return preg_replace(self::SEARCH_SET, self::REPLACEMENT_SET, $wildcard);
}

Expand All @@ -29,6 +34,6 @@ public static function compare($value, string $wildcard): bool
return true;
}

return (bool) preg_match("/{$wildcard}/ui", $value);
return (bool) preg_match("/^{$wildcard}\$/mui", $value);
}
}
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/Calculation/MathTrig.php
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ public static function SUM(...$args)
/**
* SUMIF.
*
* Counts the number of cells that contain numbers within the list of arguments
* Totals the values of cells that contain numbers within the list of arguments
*
* Excel Function:
* SUMIF(value1[,value2[, ...]],condition)
Expand Down Expand Up @@ -1327,7 +1327,7 @@ public static function SUMIF($aArgs, $condition, $sumArgs = [])
/**
* SUMIFS.
*
* Counts the number of cells that contain numbers within the list of arguments
* Totals the values of cells that contain numbers within the list of arguments
*
* Excel Function:
* SUMIFS(value1[,value2[, ...]],condition)
Expand Down

0 comments on commit 08673b5

Please sign in to comment.