6

I'm hoping someone could help me with this problem.

Say I have 3 DB tables:

Users:
user_id, user_name
100, John
101, Jessica

Cars:
car_id, car_name
30, Corvette
31, BMW


UsersCars:
user_id, car_id, car_colour
100, 30, Red
101, 30, Green
101, 31, Green

(so John got a red corvette and Jessica has a green Corvette and a BMW)

I would like to have code that returns a multidimensional PHP array something like:

Array
(
    [100] => Array
    (
        [user_id] => 100
        [user_name] => John
        [cars] => Array
        (
            [car_id]=>30,
            [car_name]=>'Corvette',
            [car_colour]=>'Red'

        )            
    )
    [101] => Array
    (
        [user_id] => 101
        [user_name] => Jessica
        [cars] => Array
        (
            [0] => Array
            (
                [car_id]=>30,
                [car_name]=>'Corvette',
                [car_colour]=>'Green'
            ),
            [1] => Array
            (
                [car_id]=>31,
                [car_name]=>'BMW',
                [car_colour]=>'Green'
            )
        )            
    )
)

I have the following SQL

SELECT u.*, c.* FROM Users u
LEFT JOIN UsersCars uc ON u.user_id = uc.user_id
LEFT JOIN Cars c ON uc.car_id = c.car_id

And PHP

$result = mysqli_query($db, $q);
while ($row = mysqli_fetch_assoc($result)) {
    $users_with_cars[$row['user_id']] = $row;
}

But this isn't correct. Anyone knows how to solve this resulting the above array (with performance in mind)? I rather do not want to hard-coded the exception of "cars" being something that can happen more than once. I rather have something that just looks at $row and $users_with_cars and when seeing some new value, it appends it, by converting the old value into an array. Maybe there's already a native PHP function for this? Or better, maybe my MySQL or whole approach is wrong?

Any help or tips appreciated.

Regards


UPDATE SOLVED

Here's an update, maybe I can help someone else how I solved it eventually.

I ended up with always using an array for one or more cars, and I adjusted the tables to always have an "id" as column name. This way you can easily expand it. See example;

Users:
id, name
100, John
101, Jessica

Cars:
id, name
30, Corvette
31, BMW


UsersCars:
user_id, car_id, car_colour
100, 30, Red
101, 30, Green
101, 31, Green



$q = 'SELECT u.*, c.id as car_id, c.name as car_name, uc.colour as car_colour FROM Users u
LEFT JOIN UsersCars uc ON u.id = uc.user_id
LEFT JOIN Cars c ON uc.car_id = c.id';

$result = mysqli_query($db, $q);
while ($row = mysqli_fetch_assoc($result)) {
    $users_with_cars[] = $row;
}
$joins = array('cars' => array('car_id'=>'id','car_name'=>'name','car_colour'=>'colour'));
$users_with_cars = create_join_array($users_with_cars, $joins);

print_r($users_with_cars);



function create_join_array($rows, $joins){
    /* build associative multidimensional array with joined tables from query rows */

    foreach((array)$rows as $row){
        if (!isset($out[$row['id']])) {
            $out[$row['id']] = $row;
        }

        foreach($joins as $name => $item){
            unset($newitem);
            foreach($item as $field => $newfield){
                unset($out[$row['id']][$field]);
                if (!empty($row[$field]))
                    $newitem[$newfield] = $row[$field];
            }
            if (!empty($newitem))
                $out[$row['id']][$name][$newitem[key($newitem)]] = $newitem;
        }
    }

    return $out;
}

This all results in the beautiful array:

Array
(
    [100] => Array
    (
        [id] => 100
        [name] => John
        [cars] => Array
        (
            [30] => Array
            (
                [id]=>30
                [name]=>'Corvette',
                [colour]=>'Red'
            )
        )            
    )
    [101] => Array
    (
        [id] => 101
        [name] => Jessica
        [cars] => Array
        (
            [30] => Array
            (
                [id]=>30,
                [name]=>'Corvette',
                [colour]=>'Green'
            ),
            [31] => Array
            (
                [id]=>31,
                [name]=>'BMW',
                [colour]=>'Green'
            )
        )            
    )
)

Let's say the users also can have multiple bikes. Then, you have multiple join arrays, you can easily bind on with left joins and add it to the join array.

$q = 'SELECT u.*, c.id as car_id, c.name as car_name, uc.colour as car_colour, b.id as bike_id, b.name as bike_name FROM Users u
LEFT JOIN UsersCars uc ON u.user_id = uc.user_id
LEFT JOIN Cars c ON uc.car_id = c.id
LEFT JOIN UsersBikes ub ON u.user_id = ub.user_id
LEFT JOIN Bikes b ON ub.bike_id = b.id';

$result = mysqli_query($db, $q);
while ($row = mysqli_fetch_assoc($result)) {
    $users_with_cars_bikes[] = $row;
}

$joins = array('cars' => array('car_id'=>'id', 'car_name'=>'name', 'car_colour'=>'colour'), 
               'bikes' => array('bike_id'=>'id', 'bike_name'=>'name'));
$users_with_cars_bikes = create_join_array($users_with_cars_bikes, $joins);

print_r($users_with_cars_bikes);

Would result in something like

Array(
    [100] => Array
    (
        [id] => 100
        [name] => John
        [cars] => Array
        (
            [30] => Array
            (
                [id]=>30
                [name]=>'Corvette',
                [colour]=>'Red'
            )
        )
        [bikes] => Array
        (
            [41] => Array
            (
                [id]=>41
                [name]=>'BMX'
            )
        )           
    )
)

and so on..

Thanks all for helping out :)

4
  • No, there's no native php function for this. You'll have to build the array yourself. check if a user's been saved in the array before. if not, store the basic user info plus the car that came with it. if the user exists, thens imply add the car data. Commented Nov 9, 2012 at 14:44
  • (BTW: to your [cars] array) I just get annoyed about some web service that returns a specific variable once as single string or array and in another result set as an array of arrays. Thus I need to check each value on client if it has more children or not. It would be easier to read the data again if you have a data structure that didn't change for different result sets. So your [cars] array should always contain a "parent" array for any cars, even with a single entry Commented Nov 9, 2012 at 20:17
  • @feeela Yeah agree thx, I wasn't sure yet about this design choice. Of course you could cast it as well while using it: foreach ((array)$cars as $car){} but that makes no sense Commented Nov 11, 2012 at 1:05
  • @Sanne Not if the casted value is an object. (array) new StdClass() also returns an array. But what you will get is not an array containing a single object (array( new StdClass() )), but the object's members in an array. Commented Nov 11, 2012 at 1:20

2 Answers 2

2

This is the thing I could come up with. It will (probably) create an array just like your desired output. Not sure if it will work, I wrote this here without testing. Let me know! :)

$result = mysqli_query($db, $q);
while ($row = mysqli_fetch_assoc($result)) 
{
    // Add user ID and name to the array
    $users_with_cars[$row['user_id']]['user_id'] = $row['user_id'];
    $users_with_cars[$row['user_id']]['user_name'] = $row['user_name'];
    // Check if this user has cars in the array. If not, this is the first car (see else)
    if(isset($users_with_cars[$row['user_id']]['cars']))
    {
        // Check if there is exactly 1 car in the array
        if(count($users_with_cars[$row['user_id']]['cars']) == 1)
        {
            // If yes, put that car in a 'sub array'
            $users_with_cars[$row['user_id']]['cars'] = array(0 => $users_with_cars[$row['user_id']]['cars']);
            // Then add the new car
            $users_with_cars[$row['user_id']]['cars'][] = array('car_id' => $row['car_id'], 'car_name' => $row['car_name'], 'car_color' => $row['car_color']);
        }
        else
        {
            // It already has more than one car in the array. Just add it
            $users_with_cars[$row['user_id']]['cars'][] = array('car_id' => $row['car_id'], 'car_name' => $row['car_name'], 'car_color' => $row['car_color']);
        }
    }
    else
    {
        // Add a single car without 'sub array'
        $users_with_cars[$row['user_id']]['cars']['car_id'] = $row['car_id'];
        $users_with_cars[$row['user_id']]['cars']['car_name'] = $row['car_name'];
        $users_with_cars[$row['user_id']]['cars']['car_color'] = $row['car_color'];
    }
}


EDIT:

This is an example as I mentioned in my comment:

$result = mysqli_query($db, $q);
while ($row = mysqli_fetch_assoc($result)) 
{
    $count = count($users_with_cars[$row['user_id']]['cars']);
    foreach($row AS $key => $value)
    {
        if(substr($key, 0, 4) == "car_")
        {
            // Single:
            $users_with_cars[$row['user_id']]['cars'][$key] = $value;
            // Multiple:
            $users_with_cars[$row['user_id']]['cars'][$count][$key] = $value;
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks yeah, this code is correct I think, as is a part of Sergey's code. But basically; there's no native quick solution? It strikes me a bit since so many designs have linked tables and need to convert it to a multi assoc array.. :/ These solutions are also a bit painful, since if you alter the DB structure (add engine column to car), you have to modify your code as well.
@Sanne Unfortunately there is no native function to do something like this. It may be annoying, but this is probably the best way to go. If you want it more dynamic, you could loop through $row and add every column. Since the data from cars starts with car_, you could check if a column starts with car_, if so, add it to the array. That would mean you won't have to change the code if you add a specification.
Thanks, inspired me of a solution
0
$result = mysqli_query($db, $q);
$users_with_cars = array();
while ($row = mysqli_fetch_assoc($result)) {
    if (!isset($users_with_cars[$row['user_id']])) {
        $user = array();
        $user['user_id'] = $row['user_id'];
        $user['user_name'] = $row['user_name'];
        $user['cars'] = array();
        $users_with_cars[$row['user_id']] = $user;
    }
    $car = array();
    $car['car_id'] = $row['car_id'];
    $car['car_name'] = $row['car_name'];
    $car['car_colour'] = $row['car_colour'];
    $users_with_cars[$row['user_id']]['cars'] = $car;
}

1 Comment

Thanks, inspired me of a solution

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.