【问题标题】:What is the optimized/best way to retrieve data from two tables?从两个表中检索数据的优化/最佳方法是什么?
【发布时间】:2022-01-02 18:34:41
【问题描述】:

我有两张桌子。

post table

|post_id | post_title |
+--------+------------+
| 1      | Post 1     |
| 2      | Post 2     |
| 3      | Post 3     |

post_creator table

|post_id | creator |
+--------+---------+
| 1      | John    | 
| 1      | Smith   | 
| 1      | Mike    |
| 2      | Bob     |
| 3      | Peter   |
| 3      | Brad    |

当我加入这些表时,它看起来像这样。

SELECT * 
FROM post p
JOIN post_creator c ON p.post_id = c.post_id


|post_id | post_title | post_id | creator|
+----------------------------------------+
| 1      | Post 1     | 1       | John   |
| 1      | Post 1     | 1       | Smith  |
| 1      | Post 1     | 1       | Mike   |
| 2      | Post 2     | 2       | Bob    |
| 3      | Post 3     | 3       | Peter  |
| 3      | Post 3     | 3       | Brad   |

我想抓住每个帖子的创建者。但在这种情况下,我的加入的结果由于创作者的原因而一次又一次地重复了相同的帖子

我首先从 post 表中获取所有数据。然后我循环该结果并在循环内获取每个帖子的所有创建者。但是在这种情况下,它会一次又一次地查询每个内容以获取创建者。

$sql = "SELECT * FROM post";
$stmt = $conn->prepare($sql);
$stmt->execute();
$res = $stmt->fetchAll(PDO::FETCH_OBJ);

$dataObj = new stdClass;
$dataArr = [];

foreach($res as $post){
  $sql = "SELECT creator FROM post_creator WHERE post_id=$post->post_id";
  $stmt = $conn->prepare($sql);
  $stmt->execute();
  $creators = $stmt->fetchAll(PDO::FETCH_OBJ);
   
  $dataObj->post_id = $post->post_id
  $dataObj->post_title = $post->title
  $dataObj->creators = $creators;
  
  array_push($dataArr, $dataObj);
}

所以我的dataArr 终于有了这种结构。

[
  {
    post_id: 1, 
    post_title: Post 1, 
    creators:[John, Smith, Mike] 
  },
  
  {
    post_id: 2, 
    post_title: Post 2, 
    creators:[Bob] 
  },

  {
    post_id: 2, 
    post_title: Post 1, 
    creators:[Peter, Brad] 
  },
]

这就是我想要的。现在我可以循环并渲染到一个视图。

是否有任何优化/更好的方法来获得此结果而无需一次又一次地循环和查询?

【问题讨论】:

    标签: php sql join


    【解决方案1】:

    我认为您需要使用group_concat 对您的creators 进行分组。

    SELECT p.post_id, post_title, group_concat(creator) 
    FROM post p
    JOIN post_creator using(post_id) 
    group by p.post_id
    

    另外,这个:

    $sql = "SELECT creator FROM post_creator WHERE post_id=$post->post_id";
    $stmt = $conn->prepare($sql);
    $stmt->execute();
    

    是对准备好的语句的不当使用。应该写成:

    $sql = "SELECT creator FROM post_creator WHERE post_id=?";
    $stmt = $conn->prepare($sql);
    $stmt->execute(array($post->post_id));
    

    如果需要,但不是。始终绑定值,从不直接放入 SQL。

    【讨论】:

    • 感谢您的回答。我使用了explode,并将创建者作为结果中的一个数组。您能否解释一下Always bind values, never put direct to SQL.您的回答声明。
    • 他说的是使用准备好的语句(见链接)。您只是将值连接到 SQL (WHERE post_id=$post->post_id) 的部分是非常有问题的(并且存在安全风险)。 -- stackoverflow.com/questions/60174/…
    • @Raxi 感谢您的信息? 它有帮助。
    【解决方案2】:

    我想说你可以走 3 条不同的道路,它们都有一些好处。

    选项 1. 使用 JOIN(和重叠行)的简单 SELECT 查询

    这或多或少是您已经尝试过的,您列出的第一个查询;这导致重复的行。

    修改你的应用程序代码来处理欺骗是相当简单的,只需将创建者折叠到同一个数组/对象中。开销也几乎为零。从关系数据库设计的角度来看,这种方法仍然是最佳实践。

       SELECT p.post_id
            , p.post_title
            , c.creator
         FROM post         p
    LEFT JOIN post_creator c 
           ON p.post_id = c.post_id
     ORDER BY p.post_id ASC
    

    .

    /* $rows = ...query...; */
    $posts = [];
    foreach ($rows as $row) {
        if (!isset($posts[( $row['post_id'] )])) {
            // this is a new post_id
            $post                       = [];
            $post['id']                 = $row['post_id'];
            $post['creators']           = [];
            $post['creators'][]         = $row['creator'];
            $posts[( $row['post_id'] )] = $post;
        } else {
            // this is just an additional creator
            $posts[( $row['post_id'] )]['creators'][] = $row['creator'];
        }
    }
    

    选项 2. 多值列(数组或 json)

    对于非纯粹主义者来说,稍微实用一点的解决方案是让您的查询生成包含多个值的输出列。这通常意味着 JSON 或 ARRAY 列。具体细节取决于您选择的数据库系统。

    在任何一种情况下,您都可以将它与 SQL GROUP BY 功能结合使用。 假设您使用 MySQL 并且更喜欢 JSON 类型;然后,您将使用以下查询:

        SELECT p.post_id
             , p.post_title
             , JSON_ARRAYAGG(c.creator) AS creators
          FROM post         p
     LEFT JOIN post_creator c 
            ON p.post_id = c.post_id
      GROUP BY p.post_id
      ORDER BY p.post_id ASC
    

    这样,每个帖子您只会收到一条记录,并且您将获得诸如 ['Mike', 'Paul', 'Susan'] 之类的值,json_decode() 可以将其转换为正确的 PHP 数组。

    选项 3. 完整文档

    另一种基于选项 #2 的替代方案是完全使用 JSON,并完全放弃关系记录集。

    大多数现代 DBMS 都具有大量 JSON 功能,并且您自己列为 dataArr 的格式可以完全由数据库生成以响应单个 SELECT 查询。

    这样,查询总是会产生只有 1 行和 1 列的结果,其中包含整个 dataArr 组合所有这些帖子(同样,可以使用 json_decode 将其转换为原生 PHP 数组或对象树,就像以前一样)。

    虽然此方法的结果可能非常简洁(取决于您的应用程序的编写方式),但有些人可能想知道您为什么使用 RDBMS 而不是 MongoDB 之类的东西。


    总的来说,我会推荐选项 1。

    【讨论】:

    • 感谢全面的回答。但它说 JSON_ARRAYGG 不存在。
    • 它是JSON_ARRAYAGG;但是是的,这是某些 MySQL/MariaDB 版本的示例;我认为您没有提到您使用的是哪种/版本的数据库——dev.mysql.com/doc/refman/8.0/en/json-function-reference.html
    • Server type: MariaDB , Server version: 10.4.19-MariaDB , PHP version: 8.0.7 这些是版本。
    • 阿耶。看起来 MariaDB 直到 10.5.x 分支才添加它,所以你的版本还没有它。 mariadb.com/kb/en/json_arrayagg -- 然而,有许多替代方法可以做到这一点。您可以查询JSON_QUOTE(c.creator),它提供了一个正确转义和预先引用的json值,然后GROUP_CONCAT将它们全部组合成一个值。
    • 是的...有效。谢谢提供信息。 ??
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 1970-01-01
    • 2012-07-14
    • 2015-04-08
    • 2017-08-17
    相关资源
    最近更新 更多