Skip to content

Commit a5a3de0

Browse files
frantzmiccoliguillaume-ro-fr
authored andcommitted
Exact match in VLOOKUP now returns first match
It was inconsistent with spreadsheet software before. Closes PHPOffice#809
1 parent 040cd9a commit a5a3de0

File tree

4 files changed

+70
-23
lines changed

4 files changed

+70
-23
lines changed

CHANGELOG.md

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

2122
## [1.5.2] - 2018-11-25
2223

src/PhpSpreadsheet/Calculation/LookupRef.php

+38-23
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $hei
421421
* @param mixed $index_num Specifies which value argument is selected.
422422
* Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number
423423
* between 1 and 254.
424-
* @param mixed $value1... Value1 is required, subsequent values are optional.
424+
* @param mixed $value1 ... Value1 is required, subsequent values are optional.
425425
* Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on
426426
* index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or
427427
* text.
@@ -709,24 +709,33 @@ public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not
709709

710710
$rowNumber = $rowValue = false;
711711
foreach ($lookup_array as $rowKey => $rowData) {
712+
// break if we have passed possible keys
712713
if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
713714
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) {
714715
break;
715716
}
716717
// remember the last key, but only if datatypes match
717718
if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) ||
718719
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) {
719-
$rowNumber = $rowKey;
720-
$rowValue = $rowData[$firstColumn];
720+
if ($not_exact_match) {
721+
$rowNumber = $rowKey;
722+
$rowValue = $rowData[$firstColumn];
723+
724+
continue;
725+
} elseif ((strtolower($rowData[$firstColumn]) == strtolower($lookup_value))
726+
// Spreadsheets software returns first exact match,
727+
// we have sorted and we might have broken key orders
728+
// we want the first one (by its initial index)
729+
&& (($rowNumber == false) || ($rowKey < $rowNumber))
730+
) {
731+
$rowNumber = $rowKey;
732+
$rowValue = $rowData[$firstColumn];
733+
}
721734
}
722735
}
723736

724737
if ($rowNumber !== false) {
725-
if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
726-
// if an exact match is required, we have what we need to return an appropriate response
727-
return Functions::NA();
728-
}
729-
// otherwise return the appropriate value
738+
// return the appropriate value
730739
return $lookup_array[$rowNumber][$returnColumn];
731740
}
732741

@@ -764,29 +773,35 @@ public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not
764773
if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array))) {
765774
return Functions::REF();
766775
}
767-
$columnKeys = array_keys($lookup_array[$firstRow]);
776+
768777
$firstkey = $f[0] - 1;
769778
$returnColumn = $firstkey + $index_number;
770779
$firstColumn = array_shift($f);
771-
772-
if (!$not_exact_match) {
773-
$firstRowH = asort($lookup_array[$firstColumn]);
774-
}
775-
$rowNumber = $rowValue = false;
780+
$rowNumber = null;
776781
foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) {
777-
if ((is_numeric($lookup_value) && is_numeric($rowData) && ($rowData > $lookup_value)) ||
778-
(!is_numeric($lookup_value) && !is_numeric($rowData) && (strtolower($rowData) > strtolower($lookup_value)))) {
782+
// break if we have passed possible keys
783+
$bothNumeric = is_numeric($lookup_value) && is_numeric($rowData);
784+
$bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData);
785+
if (($bothNumeric && $rowData > $lookup_value) ||
786+
($bothNotNumeric && strtolower($rowData) > strtolower($lookup_value))) {
779787
break;
780788
}
781-
$rowNumber = $rowKey;
782-
$rowValue = $rowData;
783-
}
784789

785-
if ($rowNumber !== false) {
786-
if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
787-
// if an exact match is required, we have what we need to return an appropriate response
788-
return Functions::NA();
790+
// Remember the last key, but only if datatypes match (as in VLOOKUP)
791+
if ($bothNumeric || $bothNotNumeric) {
792+
if ($not_exact_match) {
793+
$rowNumber = $rowKey;
794+
795+
continue;
796+
} elseif (strtolower($rowData) === strtolower($lookup_value)
797+
&& ($rowNumber === null || $rowKey < $rowNumber)
798+
) {
799+
$rowNumber = $rowKey;
800+
}
789801
}
802+
}
803+
804+
if ($rowNumber !== null) {
790805
// otherwise return the appropriate value
791806
return $lookup_array[$returnColumn][$rowNumber];
792807
}

tests/data/Calculation/LookupRef/HLOOKUP.php

+10
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,14 @@
275275
2,
276276
true,
277277
],
278+
[
279+
5,
280+
'x',
281+
[
282+
['Selection column', '0', '0', '0', '0', 'x', 'x', 'x', 'x', 'x'],
283+
['Value to retrieve', 1, 2, 3, 4, 5, 6, 7, 8, 9]
284+
],
285+
2,
286+
false
287+
]
278288
];

tests/data/Calculation/LookupRef/VLOOKUP.php

+21
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,25 @@
291291
2,
292292
true,
293293
],
294+
[
295+
5,
296+
'x',
297+
[
298+
[
299+
'Selection column',
300+
'Value to retrieve',
301+
],
302+
['0', 1],
303+
['0', 2],
304+
['0', 3],
305+
['0', 4],
306+
['x', 5],
307+
['x', 6],
308+
['x', 7],
309+
['x', 8],
310+
['x', 9],
311+
],
312+
2,
313+
false
314+
]
294315
];

0 commit comments

Comments
 (0)