简体   繁体   中英

Populate results of one-to-many in a single query

I have two tables: activity and tag . One activity have many tags . I need to optimize our current setup, where we find all activities inside the table and then iterate over each activity to find all tags. Finally we dispose the content as JSON.

Here is an example:

-- Query that we execute one time
SELECT `id`, `name`
FROM `activity`

-- Query that we execute N times
SELECT `id`, `name`, `color`
FROM `tag`
WHERE `tag`.`activityId` = $activityId

So our final code looks something like that:

$result = mysqli_query("
    SELECT `id`, `name`
    FROM `activity`
");

$data = array();

for ($i=0; $i<mysqli_num_rows($result); $i++) {
    $row = mysqli_fetch_assoc($result);

    $data[$i]["activityId"] = $row["id"];
    $data[$i]["name"] = $row["name"];
    $data[$i]["tags"] = array();

    $activityId = $row["id"];

    $resultTags = mysqli_query("
        SELECT `id`, `name`, `color`
        FROM `tag`
        WHERE `tag`.`activityId` = $activityId
    ");

    for ($j=0; $j<mysqli_num_rows($resultTags); $j++) {
        $row = mysqli_fetch_assoc($resultTags);

        $data[$i]["tags"][$j]["name"] = $row["name"];
        $data[$i]["tags"][$j]["color"] = $row["color"];
    }
}

PS: this is a pseudo-code, our queries and tables are way more complex, but that's basically the essence of our problem.

Is it possible to accomplish that using inner queries, or even better, just with joins?

Since we need to populate around 5 tables, we end up executing 5 queries for each activity we have.

Thank you.

Sure just JOIN the tag table on the activity table.

Your query should look something like this.

SELECT activity.id, activity.name, tag.name AS tagName, tag.color AS tagColor
FROM activity
JOIN tag ON tag.activityId = activity.id;

The idea that the activity table's primary key is id and that each row is unique (ie the name will never change for any given activity.id ), then the JOIN is safe even if the there are no tags. We'll get back at least N rows where N is the number of tag.activityId rows that have a corresponding id in activity .

So in PHP you can build the array with a single query like this.

$data = [];
foreach($result as $row) {

    $tags = ["name" => $row["tagName"], "color" => $row["tagColor"]];

    if (isset($data[$row["id"]])) {
        $data[$row["id"]]["tags"][] = $tags; 
    } else {
        $data[$row["id"]] = [
            [
                "id"   => $row["id"],
                "name" => $row["name"],
                "tags" => [$tags],
            ],
        ];
    }

}

Because the information in the activity table will never change, then the only thing we need to append is the tags, which we do by checking isset($data[$row["id"]]) inside the loop.

So from this SQLFiddle you can see a working example of the two tables where we have 4 rows in the activity , and 6 rows in the tag table. This gives us a total of 6 rows. Since one of the ids in the activity table doesn't have any tags it's excluded from the result set. To get back rows with 0 tags you can use a LEFT JOIN instead like this .

SELECT activity.id, activity.name,
       tag.activityId AS tagId, tag.name AS tagName, tag.color AS tagColor
FROM activity
LEFT JOIN tag ON tag.activityId = activity.id;

Which means we get null values for empty tag rows. So we can modify the PHP to look like this.

$data = [];
foreach($result as $row) {

    $tags = empty($row["tagId"]) ?: ["name" => $row["tagName"], "color" => $row["tagColor"]];

    if (isset($data[$row["id"]]) && $tags) {
        $data[$row["id"]]["tags"][] = $tags; 
    } else {
        $data[$row["id"]] = [
            [
                "id"   => $row["id"],
                "name" => $row["name"],
                "tags" => $tags ? [$tags] : [],
            ],
        ];
    }

}

So your finally output from PHP using json_encode , with the sample data in the SQLFiddle, should look something like this.

"1": {
        "id": 1,
        "name": "foo",
        "tags": [
            {
                "tagName": "tag-a",
                "tagColor": "red"
            },
            {
                "tagName": "tag-b",
                "tagColor": "green"
            },
            {
                "tagName": "tag-c",
                "tagColor": "blue"
            }
        ]
    },
    "2": {
        "id": 2,
        "name": "bar",
        "tags": [
            {
                "tagName": "tag-a",
                "tagColor": "orange"
            },
            {
                "tagName": "tag-b",
                "tagColor": "red"
            }
        ]
    },
    "3": {
        "id": 3,
        "name": "baz",
        "tags": [
            {
                "tagName": "tag-a",
                "tagColor": "purple"
            }
        ]
    },
    "4": {
        "id": 4,
        "name": "quix",
        "tags": []
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM