从两个表中检索数据的 optimized/best 方法是什么?

What is the optimized/best way to retrieve data from two tables?

我有两个 table:

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    |

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

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。但是在这种情况下,由于创建者的原因,我的合并结果一次又一次地重复相同的 post。

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

$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] 
  },
]

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

有没有什么optimized/better方法可以得到这个结果而不用反复循环和查询?

我认为你需要使用 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.

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

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

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

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

   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

这样,每个 post 您只会收到一条记录,并且您会得到一个值,例如 ['Mike', 'Paul', 'Susan']json_decode() 可以变成一个合适的 [=53] =]数组。

选项 3. 完整文件

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

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

这样,查询将始终只产生 1 行和 1 列,它包含整个 dataArr 组合所有这些 posts(同样,可以变成本机PHP 数组或对象树 json_decode,就像以前一样)。

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


总体而言,我推荐选项 1。