Exact match in VLOOKUP now returns first match

It was inconsistent with spreadsheet software before.

Closes #809
This commit is contained in:
Fräntz Miccoli 2018-12-10 16:12:51 +01:00 committed by Adrien Crivelli
parent db2621c4fe
commit 294ba58dde
No known key found for this signature in database
GPG Key ID: B182FD79DC6DE92E
4 changed files with 70 additions and 23 deletions

View File

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Improve XLSX parsing speed if no readFilter is applied - [#772](https://github.com/PHPOffice/PhpSpreadsheet/issues/772) - Improve XLSX parsing speed if no readFilter is applied - [#772](https://github.com/PHPOffice/PhpSpreadsheet/issues/772)
- Fix column names if read filter calls in XLSX reader skip columns - [#777](https://github.com/PHPOffice/PhpSpreadsheet/pull/777) - Fix column names if read filter calls in XLSX reader skip columns - [#777](https://github.com/PHPOffice/PhpSpreadsheet/pull/777)
- Fix LOOKUP function which was breaking on edge cases - [#796](https://github.com/PHPOffice/PhpSpreadsheet/issues/796) - Fix LOOKUP function which was breaking on edge cases - [#796](https://github.com/PHPOffice/PhpSpreadsheet/issues/796)
- Fix VLOOKUP with exact matches
## [1.5.2] - 2018-11-25 ## [1.5.2] - 2018-11-25

View File

@ -421,7 +421,7 @@ class LookupRef
* @param mixed $index_num Specifies which value argument is selected. * @param mixed $index_num Specifies which value argument is selected.
* Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number * Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number
* between 1 and 254. * between 1 and 254.
* @param mixed $value1... Value1 is required, subsequent values are optional. * @param mixed $value1 ... Value1 is required, subsequent values are optional.
* Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on * Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on
* index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or * index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or
* text. * text.
@ -709,6 +709,7 @@ class LookupRef
$rowNumber = $rowValue = false; $rowNumber = $rowValue = false;
foreach ($lookup_array as $rowKey => $rowData) { foreach ($lookup_array as $rowKey => $rowData) {
// break if we have passed possible keys
if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) || if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) { (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) {
break; break;
@ -716,17 +717,25 @@ class LookupRef
// remember the last key, but only if datatypes match // remember the last key, but only if datatypes match
if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) || if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) ||
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) { (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) {
if ($not_exact_match) {
$rowNumber = $rowKey;
$rowValue = $rowData[$firstColumn];
continue;
} elseif ((strtolower($rowData[$firstColumn]) == strtolower($lookup_value))
// Spreadsheets software returns first exact match,
// we have sorted and we might have broken key orders
// we want the first one (by its initial index)
&& (($rowNumber == false) || ($rowKey < $rowNumber))
) {
$rowNumber = $rowKey; $rowNumber = $rowKey;
$rowValue = $rowData[$firstColumn]; $rowValue = $rowData[$firstColumn];
} }
} }
}
if ($rowNumber !== false) { if ($rowNumber !== false) {
if ((!$not_exact_match) && ($rowValue != $lookup_value)) { // return the appropriate value
// if an exact match is required, we have what we need to return an appropriate response
return Functions::NA();
}
// otherwise return the appropriate value
return $lookup_array[$rowNumber][$returnColumn]; return $lookup_array[$rowNumber][$returnColumn];
} }
@ -764,29 +773,35 @@ class LookupRef
if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array))) { if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array))) {
return Functions::REF(); return Functions::REF();
} }
$columnKeys = array_keys($lookup_array[$firstRow]);
$firstkey = $f[0] - 1; $firstkey = $f[0] - 1;
$returnColumn = $firstkey + $index_number; $returnColumn = $firstkey + $index_number;
$firstColumn = array_shift($f); $firstColumn = array_shift($f);
$rowNumber = null;
if (!$not_exact_match) {
$firstRowH = asort($lookup_array[$firstColumn]);
}
$rowNumber = $rowValue = false;
foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) { foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) {
if ((is_numeric($lookup_value) && is_numeric($rowData) && ($rowData > $lookup_value)) || // break if we have passed possible keys
(!is_numeric($lookup_value) && !is_numeric($rowData) && (strtolower($rowData) > strtolower($lookup_value)))) { $bothNumeric = is_numeric($lookup_value) && is_numeric($rowData);
$bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData);
if (($bothNumeric && $rowData > $lookup_value) ||
($bothNotNumeric && strtolower($rowData) > strtolower($lookup_value))) {
break; break;
} }
// Remember the last key, but only if datatypes match (as in VLOOKUP)
if ($bothNumeric || $bothNotNumeric) {
if ($not_exact_match) {
$rowNumber = $rowKey; $rowNumber = $rowKey;
$rowValue = $rowData;
continue;
} elseif (strtolower($rowData) === strtolower($lookup_value)
&& ($rowNumber === null || $rowKey < $rowNumber)
) {
$rowNumber = $rowKey;
}
}
} }
if ($rowNumber !== false) { if ($rowNumber !== null) {
if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
// if an exact match is required, we have what we need to return an appropriate response
return Functions::NA();
}
// otherwise return the appropriate value // otherwise return the appropriate value
return $lookup_array[$returnColumn][$rowNumber]; return $lookup_array[$returnColumn][$rowNumber];
} }

View File

@ -275,4 +275,14 @@ return [
2, 2,
true, true,
], ],
[
5,
'x',
[
['Selection column', '0', '0', '0', '0', 'x', 'x', 'x', 'x', 'x'],
['Value to retrieve', 1, 2, 3, 4, 5, 6, 7, 8, 9]
],
2,
false
]
]; ];

View File

@ -291,4 +291,25 @@ return [
2, 2,
true, true,
], ],
[
5,
'x',
[
[
'Selection column',
'Value to retrieve',
],
['0', 1],
['0', 2],
['0', 3],
['0', 4],
['x', 5],
['x', 6],
['x', 7],
['x', 8],
['x', 9],
],
2,
false
]
]; ];