Is it possible to sort a multidimensional array by multiple columns using natural sort in PHP? Here is an example. Suppose I have a 2D array of data, e.g.,

$array[1]['Name'] = 'John';
$array[1]['Age'] = '20';
$array[1]['Code'] = 'ABC 12';

$array[2]['Name'] = 'John';
$array[2]['Age'] = '21';
$array[2]['Code'] = 'ABC 1';

$array[3]['Name'] = 'Mary';
$array[3]['Age'] = '20';
$array[3]['Code'] = 'ABC 10';

I want to sort this array by name (ASC), then by age (DESC), and by code (ASC), all will be sorted naturally. Basically that will be array_multisort with natural sort.

I found many solutions about this topic on the web. Unfortunately, they only support sorting by one column, not multiple column.

3

There are 3 best solutions below

0
On BEST ANSWER

I think you have to implement a custom comparison function for this behavior:

function myCmp($a, $b) {
 $nameCmp = strnatcasecmp($a['Name'], $b['Name']);
 $ageCmp = strnatcasecmp($a['Age'], $b['Age']);
 $codeCmp = strnatcasecmp($a['Code'], $b['Code']);

 if ($nameCmp != 0) // Names are not equal
   return($nameCmp);

 // Names are equal, let's compare age

 if ($ageCmp != 0) // Age is not equal
   return($ageCmp * -1); // Invert it since you want DESC

 // Ages are equal, we don't need to compare code, just return the comparison result
   return($codeCmp);
}

Then you can call usort($array, 'myCmp'); and should get the desired sorting

0
On

Only one of the three sorting rules actually requires natural sorting. In fact, if you use PHP's 3-way comparison operator (<=>), then it will automatically compare numeric strings as numeric values. Only the third rule needs natural sorting to be explicitly declared because it demands the comparison of alphanumeric strings.

Code: (Demo)

$array = [
    1 => ['Name' => 'John', 'Age' => '20', 'Code' => 'ABC 12'],
    2 => ['Name' => 'John', 'Age' => '21', 'Code' => 'ABC 1'],
    3 => ['Name' => 'Mary', 'Age' => '20', 'Code' => 'ABC 10'],
    4 => ['Name' => 'John', 'Age' => '20', 'Code' => 'ABC 1'],
    5 => ['Name' => 'John', 'Age' => '20', 'Code' => 'ABC 100'],
    6 => ['Name' => 'John', 'Age' => '20', 'Code' => 'ABC 2'],

];

uasort(
    $array,
    fn($a, $b) =>
        [$a['Name'], $b['Age']] <=> [$b['Name'], $a['Age']] // name (ASC) then by age (DESC)
        ?: strnatcasecmp($a['Code'], $b['Code']) // then by code (ASC / naturally)
);
var_export($array);

Output:

array (
  2 => 
  array (
    'Name' => 'John',
    'Age' => '21',
    'Code' => 'ABC 1',
  ),
  4 => 
  array (
    'Name' => 'John',
    'Age' => '20',
    'Code' => 'ABC 1',
  ),
  6 => 
  array (
    'Name' => 'John',
    'Age' => '20',
    'Code' => 'ABC 2',
  ),
  1 => 
  array (
    'Name' => 'John',
    'Age' => '20',
    'Code' => 'ABC 12',
  ),
  5 => 
  array (
    'Name' => 'John',
    'Age' => '20',
    'Code' => 'ABC 100',
  ),
  3 => 
  array (
    'Name' => 'Mary',
    'Age' => '20',
    'Code' => 'ABC 10',
  ),
)

Note the the first level keys are preserved in the output because uasort() is used. Because the input array is not indexed, I am assuming that the keys are ids or something otherwise important.


If you are comfortable with reindexing the parent level keys, then array_multisort() can be used after generating arrays of columnar data. (Demo)

foreach ($array as ['Name' => $names[], 'Age' => $ages[], 'Code' => $codes[]]);

array_multisort(
    $names,        // ASC, REGULAR
    $ages,         // DESC, NUMERIC
    SORT_DESC,
    SORT_NUMERIC,
    $codes,        // ASC, NATURAL
    SORT_ASC,
    SORT_NATURAL,
    $array         // the array to be affected
);
var_export($array);
0
On

If you're using PHP 5.4 or newer, you can use array_multisort with the SORT_NATURAL flag.