简体   繁体   English

在单个查询中填充一对多的结果

[英]Populate results of one-to-many in a single query

I have two tables: activity and tag . 我有两个表: activitytag One activity have many tags . 一个activity有很多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. 最后,我们将内容作为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. PS:这是一个伪代码,我们的查询和表格更复杂,但这基本上是我们问题的本质。

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. 由于我们需要填充5个表,我们最终会为每个活动执行5个查询。

Thank you. 谢谢。

Sure just JOIN the tag table on the activity table. 当然只需JOIN 活动表上的标签表即可。

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. 活动表的主键是id并且每行都是唯一的(即,对于任何给定的activity.id ,名称永远不会改变),即使没有标签,JOIN也是安全的。 We'll get back at least N rows where N is the number of tag.activityId rows that have a corresponding id in activity . 我们会回来至少N行,其中N是多少tag.activityId有相应的行idactivity

So in PHP you can build the array with a single query like this. 所以在PHP中,您可以使用像这样的单个查询来构建数组。

$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. 因为活动表中的信息永远不会改变,所以我们唯一需要追加的是标记,我们通过在循环中检查isset($data[$row["id"]])来做。

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. 因此,从这个SQLFiddle可以看到两个表的工作示例,其中我们在activity有4行,在tag表中有6行。 This gives us a total of 6 rows. 这给了我们总共6行。 Since one of the ids in the activity table doesn't have any tags it's excluded from the result set. 由于活动表中的一个ID没有任何标记,因此将其从结果集中排除。 To get back rows with 0 tags you can use a LEFT JOIN instead like this . 要获得带有0个标签的行,您可以使用LEFT JOIN 就像这样

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. 所以我们可以修改PHP看起来像这样。

$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. 因此,使用json_encode从PHP获得的最终输出,以及SQLFiddle中的示例数据,应该看起来像这样。

"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": []
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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