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

时间:2016-08-31 22:34:45

标签: php mysql

我有两个表:activitytag。一个activity有许多tags。我需要优化当前的设置,我们在表中查找所有活动,然后遍历每个活动以查找所有标记。最后,我们将内容作为JSON处理。

以下是一个例子:

-- 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

所以我们的最终代码看起来像这样:

$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:这是一个伪代码,我们的查询和表格更复杂,但这基本上是我们问题的本质。

是否可以使用内部查询来实现,甚至更好,只需使用连接?

由于我们需要填充5个表,我们最终会为每个活动执行5个查询。

谢谢。

1 个答案:

答案 0 :(得分:0)

确保{strong>活动表上的标记表只有JOIN

您的查询应该是这样的。

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

活动表的主键是id并且每一行都是唯一的(即,对于任何给定的activity.id,名称永远不会改变),然后JOIN是安全的,即使有没有标签。我们将至少返回N行,其中Ntag.activityId中具有相应id的{​​{1}}行的数量。

因此,在PHP中,您可以使用单个查询构建数组。

activity

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

所以从这个SQLFiddle你可以看到两个表的工作示例,其中isset($data[$row["id"]])中有4行,activity表中有6行。这给了我们总共6行。由于活动表中的一个ID没有任何标记,因此将其从结果集中排除。要获取包含0个代码的行,您可以使用tag代替like this

LEFT JOIN

这意味着我们为空标记行获取空值。所以我们可以修改PHP看起来像这样。

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;

因此,使用$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] : [], ], ]; } } 的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": []
    }
}