在嵌套的 while 循环中输出透视/链接表的内容 - MySQL / PHP PDO

Output The Contents Of A Pivot / Linking Table In A Nested While Loop - MySQL / PHP PDO

提问人:paulo77 提问时间:8/31/2022 最后编辑:paulo77 更新时间:9/9/2022 访问量:260

问:

我遇到过一种情况,我试图从与PHP的多对多关系中输出MySQL数据透视/链接表的内容。

在下面的代码中,我有一系列将包含图像的板。实际的电路板预览是用PHP的第一个块输出的,但是在里面,我需要一个嵌套的while循环来输出图像本身。

数据透视/链接表被调用有两列,它们都是表和表的外键。下面的主要代码下面给出了表格的表示形式。boards_imagesboard_idimage_idboardsimages

因为有些板上已经有其他板上的图像,我显然需要某种类型的条件逻辑,当相关板存在时输出图像。

每个板预览只会显示四个图像,因此我需要向MySQL添加一个子句LIMIT 4

我的问题

解决这个问题的最佳方法是什么,我做:

a) 需要执行两个数据库调用,一个在父 while 循环中,一个在嵌套的 while 循环中,或者我是否需要从具有多个 JOIN 的父 while 循环中的 MySQL 数据库中获取所有信息?

b) 如何实际输出数据透视/链接表的内容?我似乎不知道该怎么做。

<?php 

    // $db_id is a variable created from a user login $_SESSION value

    $sql = "SELECT boards.board_id, boards.board_name, users.user_id 
    FROM boards
    JOIN users ON boards.user_id = users.user_id 
    WHERE users.user_id = :user_id
    ORDER BY boards.board_id DESC";

    $stmt = $connection->prepare($sql);
    $stmt -> execute([
        ':user_id' => $db_id
    ]);
    
    // parent while loop that outputs the board name

    while ($row = $stmt->fetch()) {
        $dbBoardname = htmlspecialchars($row['board_name']);
?>
    <div class="board-component">
        <h2><?= $dbBoardname; ?></a></h2>

        <?php

            // --- NESTED INNER WHILE LOOP

            $SQL2 = "SELECT boards_images.board_id, boards_images.image_id, images.filename
            FROM boards_images
            JOIN images ON boards_images.image_id = images.image_id
            WHERE boards_images.board_id = :board_id
            LIMIT 4";

            $stmt2 = $connection->prepare($SQL2);
            $stmt2 -> execute([
                ':board_id' => $dbBoardId
            ]);

            while ($row2 = $stmt2->fetch()) {          
                $dbImageId = htmlspecialchars($row['image_id']);
                $dbImageFilename = htmlspecialchars($row['filename']);
            ?>
                
             <img src='<?= $wwwRoot . "/images-lib/{$dbImageFilename}" ?>' >

        <?php } ?> <!-- end of nested while loop -->
    </div>
<?php } ?> <!-- end of parent while loop -->

表格的表示形式

// 'fk' stands for foreign key

// BOARDS_IMAGES LINKING / PIVOT TABLE
+----------------+----------------+
|  board_id (fk) | image_id (fk)  |
+----------------+----------------+
|  1             |  23            |         
|  1             |  106           |
|  1             |  55            |
|  1             |  22            |
+----------------+----------------+

// BOARDS TABLE
+-------------+--------------+---------------+
|  board_id   |  board_name  |  user_id (fk) |
----------------------------------------------
|  1          |  London      |  21           |
|  2          |  France      |  21           |
+-------------+--------------+---------------+

// IMAGES TABLE
+-------------+--------------------+---------------+
|  image_id   |  filename          | user_id (fk)  |
---------------------------------------------------+
|  23         |  BigBen.jpeg       | 21            |
|  106        |  TowerBridge.jpeg  | 21            |
|  55         |  TheMall.jpg       | 21            |
|  22         |  BuckPalace.jpg    | 21            |
+-------------+--------------------+---------------+

// USERS TABLE
+-----------------+----------------+
|  user_id        |  username      |
+-----------------+----------------+
|   21            |  johndoe       |         
+-----------------+----------------+
php mysql sql 嵌套

评论

0赞 geertjanknapen 8/31/2022
好吧,您首先需要获取属于该板的图像。然后像我想象的那样循环使用它们?
0赞 paulo77 8/31/2022
@geertjanknapen但是我如何才能从链接表中输出与它们相关的特定图像?board_id
0赞 paulo77 9/1/2022
@geertjanknapen 我已经添加了我正在使用的代码,这些代码在嵌套的 while 循环上不起作用。
0赞 Rick James 9/1/2022
我迷失在你的措辞和榜样中。这 3 种说法正确吗?板 :: 图像是多对多的;每块板属于一个用户;每张图片都属于一个用户
0赞 paulo77 9/1/2022
@RickJames是的,这 3 种说法是正确的。

答:

1赞 Simeon 9/6/2022 #1

a) 需要做两个数据库调用,一个在父 while 循环中,一个在 while 循环中 在嵌套的 while 循环中,或者我是否需要从 MySQL数据库在父while循环中使用多个JOIN?

你可以做一个查询来获取所有必要的数据,我们可以限制 php 中显示的图像数量,并且不需要加入 users 表,因为你在 boards 表中有 FK:

SELECT boards.board_name, images.filename
FROM boards
INNER JOIN boards_images on boards_images.board_id = boards.board_id
INNER JOIN images        on boards_images.image_id = images.image_id
WHERE boards.user_id = :user_id

b) 如何实际输出数据透视/链接表的内容?我 似乎不知道该怎么做。

上述查询的输出结果如下:

board_name | filename
-------------------------------
London     |  BigBen.jpeg     
London     |  TowerBridge.jpeg
London     |  TheMall.jpg     
London     |  BuckPalace.jpg 
France     |  BigBen.jpeg     
France     |  TowerBridge.jpeg
France     |  TheMall.jpg     
France     |  BuckPalace.jpg 

你的 php 循环可能看起来像这样:

<?php 

    // $db_id is a variable created from a user login $_SESSION value

    $sql = "SELECT boards.board_name, images.filename
            FROM boards
            INNER JOIN boards_images on boards_images.board_id = boards.board_id
            INNER JOIN images        on boards_images.image_id = images.image_id
            WHERE boards.user_id = :user_id";

    $stmt = $connection->prepare($sql);
    $stmt -> execute([
        ':user_id' => $db_id
    ]);

$dbBoardname_last = '';
$imgcount = 0;
 while ($row = $stmt->fetch()) {
   $dbBoardname = htmlspecialchars($row['board_name']);
   $dbImageFile = "$wwwRoot/images-lib/" . htmlspecialchars($row['filename']);
    
    //if the boardname is the same as the previous row only add images. 
   if($dbBoardname != $dbBoardname_last) 
   {
    //reset the image count for new boards
     $imgcount = 0;
     echo "<div class=\"board-component\"><h2>$dbBoardname</a></h2>";
   }
   
   //By counting the images within the loop we can avoid using multiple or nested queries to the DB 
   if($imgcount < 4) { 
    echo "<img src=\"$dbImageFile\">";
   }
   $imgcount++; 
   
   if($dbBoardname != $dbBoardname_last) 
   {
     echo '</div>';
   } 
    //record the last board_name to check if a new board element should be created
   $dbBoardname_last = $dbBoardname;
 }
?>

注意:希望这段代码能按您的预期工作,但从长远来看,我认为最佳实践是将 SQL 输出解析为 JSON 对象并对其进行迭代,否则代码可能会更清晰

评论

0赞 paulo77 9/7/2022
嗨,@Simeononon - 感谢您抽出宝贵时间回答。在您从表中选择的初始 MySQL 中,但现在在 boards 表中?filenameboardsfilename
1赞 Simeon 9/7/2022
嗨,@Emmy,正在从连接到板表的图像表中选择文件名,我已经更新了代码以反映这一点
0赞 paulo77 9/8/2022
嗨@Simeononon我建议对答案进行一些编辑,并意识到其中一项编辑不正确(目前正在等待编辑审查),即“if($imgcount <= 4)”确实应该保留为“if($imgcount < 4)”。它几乎可以工作,但如果你接受或拒绝这些变化,我可以解释正在发生的事情。
0赞 Simeon 9/8/2022
为什么 select 语句中需要boards.board_id?它根本没有在页面中使用,我已经修复了丢失的分号 - 很好发现
0赞 paulo77 9/8/2022
抱歉,它被用于更复杂的代码版本。是的,请忽略。昨天是狂躁的一天。
0赞 Lajos Arpad 9/6/2022 #2

运行查询、循环结果以及针对每个结果运行另一个查询的方法是一种次优方法,但它有效。您可能想尝试一下,因为它很简单,而且您担心的性能问题很可能永远不会实现,请阅读以下内容:https://stackify.com/premature-optimization-evil/

当然,缺点是,您不是运行单个命令,而是运行 n + 1 命令(假设板数为 n)。查询成本很高,因此,如果您已经遇到一些与此相关的性能问题,或者想要编写性能非常高的代码,那么您可以:

  • 编写单个查询
  • 该子句的第一个表是FORMboards
  • LEFT JOIN boards_images和 ,并具有适当的标准images
  • 生成的记录将包含 和 数据,如果没有足够的匹配项,则默认为boardsimagesnull
SELECT ...
FROM boards
JOIN users
ON boards.user_id = users.user_id
LEFT JOIN boards_images bi1
ON boards.board_id = bi1.board_id
LEFT JOIN images i1
ON bi1.image_id = i1.image_id
LEFT JOIN boards_images bi2
ON boards.board_id = bi2.board_id AND bi2.board_id NOT IN (bi1.board_id)
LEFT JOIN images i2
ON bi2.image_id = i2.image_id
LEFT JOIN boards_images bi3
ON boards.board_id = bi3.board_id AND bi3.board_id NOT IN (bi1.board_id, bi2.board_id)
LEFT JOIN images i3
ON bi3.image_id = i3.image_id
LEFT JOIN boards_images bi4
ON boards.board_id = bi4.board_id AND bi4.board_id NOT IN (bi1.board_id, bi2.board_id, bi3.board_id)
WHERE users.user_id = :user_id

循环返回的记录并使用返回的值。我省略了子句的内容供您填写,您将需要那里的板和图像数据,图像数据列最好以图像索引为后缀,因此您可以迭代索引并在循环中构建列名。SELECT