Use the results of a while loop in another while loop

浪子不回头ぞ 提交于 2020-12-13 05:11:36

问题


I'm having big trouble trying to create a while loop within a while loop. The first while loop is supposed to output categories. The second while loop is supposed to output the links associated with the category. The link information is in a links table and the categories information in the category table. I'm trying to do a JOIN on links.catID on category.ID.

Links table:
id links url catID type

Category table:
id cat type

links.catID is supposed to equal category.id in ints. category.cat would then output the name of the category.

The code I have is:

require 'db/connect.php';
$sql = "SELECT * FROM category";
$results = $db->query($sql);
echo "<p class='post-footer align-left'><span class='pageName'><span 
class='style3'>Links</span></span></p>";
    if($results->num_rows){
        while($row = $results->fetch_object()){
        echo "<table width='507' border='1'>";
        echo " <p class='post-footer align-left'><span class='pageName'><span class='style3'>{$row->cat}</span></span></p>";
        $sql1 = "SELECT links.url, links.links, links.catID FROM links INNER JOIN category on category.id WHERE category.id = links.catID";
        $results1 = $db->query($sql1);
            if ($results1->num_rows > 0) {
                while($row1 = $results1->fetch_object()){
                    echo "<th><span class='pageName'><span class='style3'>{$row1->links}</span></span></th>";
                }
            }
        }
    }

Please be kind to me, I have very little experience and that very likely shows in the above. I have been stuck on this for a while now and just can't figure it out. I would really appreciate someone's help.

I think I have it explained properly above :)

Don't worry about the CSS of the PHP one. The CSS is easy to fix. Just need to only list the right links with the right categories.


回答1:


As a follow-up to my first answer, the example below shows how you can do this with a single query. IMHO, you should always opt for this approach. If you only have a handful of categories to drill into, it may not be much of a performance drain if you stick with the query-nested-inside-a-loop approach. But if you have a sizable number of categories, the approach below should be significantly more efficient:

require 'db/connect.php';
$sql = "
    SELECT
        category.id
        , links.links
    FROM
        category
    LEFT JOIN
        links ON links.catID = category.id
    ORDER BY
        category.id ASC
        -- this ORDER BY is critical, it ensures that all of the
        -- records will be lumped by category
";
// (yes, this query will create multiple rows for every category, but that's OK,
// we're going to deal with that in the while() loop
$results = $db->query($sql);
echo "<p class='post-footer align-left'><span class='pageName'><span class='style3'>Links</span></span></p>";
if($results->num_rows){
    $previousId = '';
    while($row = $results->fetch_object()){
        $currentId = $row->id;
        if ($currentId !== $previousId) {
            // we only show the wrapper <table>, and the category header, if we've encountered a new
            // batch of rows which signifies that a new category is being processed
            echo "<table width='507' border='1'>";
            echo " <p class='post-footer align-left'><span class='pageName'><span class='style3'>{$row->cat}</span></span></p>";
        }
        echo "<th><span class='pageName'><span class='style3'>{$row->links}</span></span></th>";
        if ($currentId !== $previousId and $previousId !== '') {
            echo "</table>";  // this wasn't in your original code, but I assume it should be here
        }
        $previousId = $currentId;
    }
}
echo "</table>";



回答2:


The comments under the original question indicate:

The thing is that it populates all the links in the database under each of the categories.

Your problem comes from the fact that your nested query is a static string.

In this query:

$sql1 = "SELECT links.url, links.links, links.catID FROM links INNER JOIN category on category.id WHERE category.id = links.catID";

There is no variable. So no matter which category you're currently evaluating in the while loop, it looks like you're probably getting back all the links for all the categories. I'm assuming that you wanted to do something like this?

$sql1 = "SELECT links.url, links.links, links.catID FROM links INNER JOIN category on category.id WHERE category.id = links.catID AND category.id = {$row->id}";

Also, please keep in mind that your overall approach is inefficient. Almost any time you're calling a query for every iteration of a while loop, it's inefficient. Ideally, you would pull back all of this data in the first query, using a proper set of JOINs, and then filter through them in the while loop.




回答3:


Some principles to follow:

  • Avoid writing php code which contains html tags in it.
  • Don't mix the php code which fetches db data with the html code. In my code you'll notice that I fetched and processed the db data only on the upper side of the web page. So completely separated from the html code. In the html code I am using only the arrays. Why is this important? Because, for example, if a web designer should customize the design of your pages, he should be able to change the html codes without beeing obliged (by your code) to learn any database access commands. Another reason is, that, at some point, you will probably want to separate the data access code from the html code. You can only do this without mixing them. So, in short, the html code should not do any processing, but only reading operations (from an array, from variables).
  • In general, avoid using while, or do while constructs. They introduce a big risc of infinite loops, which can break your apps, or systems. In case of the while loops used in mysqli, it would be ok to use them, but with caution. Make sure that no infinite loops can be activated.
  • Consider each request made by your codes to a database as an expensive operation. For example, if you are fetching a list with 500 categories, and, for each category, you are making a new request to database to fetch the corresponding links list, then you'll "overwhelm" your web server and database.
  • Consider applying prepared statements instead of directly querying the database with query. Make yourself a habit from always using them. They are very important, because with them you are avoiding the so-called mysql injections.
  • Consider naming the tables with plurals. Like "categories". And don't hesitate to give good understandable database/table/column names. Use the form "this_is_the_name" for them (with separating underscore and small characters). In php use the "camelCase" syntax for the php variable/function/method names. Like $mysqlConnection. The id's of the html controls should be camelCase too: "thisIsAnInputId". And the css classes like this: "this-is-a-div-class".
  • Write echo '<table width="507" border="1">';. It looks better, isn't it? I am using the single quotes almost everywhere, instead of double quotes. In rare occasions I must escape the single quote character with a backslash, like this: echo 'I don\'t know';

Some good resources:

  • This website.
  • Understanding JOINs in MySQL and Other Relational Databases
  • PSR 1 and PSR 2 on this website.
  • Clean, high quality code guide

index.php:

<?php
require 'db/connect.php';

/*
 * The SQL statement. The external values are passed directly into it.
 * Therefore, the code is open to SQL injections. In order to avoid
 * them, an sql statement receiving external values should always be
 * prepared for execution.
 *
 * @link http://php.net/manual/en/mysqli.prepare.php
 */
$sql = 'SELECT
            c.id AS categoryId,
            c.cat,
            c.type,
            l.id AS linkId,
            l.url,
            l.links
        FROM links AS l
        INNER JOIN category AS c ON l.catID = c.id';

/*
 * Performs a query on the database.
 *
 * Returns FALSE on failure [<< and raises warnings, fatal errors, exceptions, etc >>].
 * For successful SELECT, SHOW, DESCRIBE or EXPLAIN queries mysqli_query() will return a
 * mysqli_result object. For other successful queries will return TRUE.
 *
 * @link https://secure.php.net/manual/en/mysqli.query.php
 */
$result = $db->query($sql);

/*
 * Fetch the data into an array like this:
 *
 * Array
 * (
 *     [0] => Array
 *         (
 *             [categoryId] => 1
 *             [cat] => Platforms
 *             [type] =>
 *             [linkId] => 1
 *             [url] => #
 *             [links] => CSI (Internet Explorer)
 *         )
 *
 *     [1] => Array
 *         (
 *             [categoryId] => 1
 *             [cat] => Platforms
 *             [type] =>
 *             [linkId] => 2
 *             [url] => #
 *             [links] => Customer view of Service Cloud
 *         )
 *
 *     [...] => [...]
 *
 *     [5] => Array
 *         (
 *             [categoryId] => 2
 *             [cat] => Engineering
 *             [type] =>
 *             [linkId] => 6
 *             [url] => #
 *             [links] => Check Returned OPTs
 *         )
 *
 *     [6] => Array
 *         (
 *             [categoryId] => 2
 *             [cat] => Engineering
 *             [type] =>
 *             [linkId] => 7
 *             [url] => #
 *             [links] => Eng Links
 *         )
 *
 *     [...] => [...]
 *
 * )
 */
$data = $result->fetch_all(MYSQLI_ASSOC);

/*
 * Iterate through the fetched data and save it into a new array, with
 * a structure suited for the required display. The new array looks like this:
 *
 * Array
 * (
 *     [1] => Array
 *         (
 *             [categoryName] => Platforms
 *             [categoryType] => red
 *             [links] => Array
 *                 (
 *                     [0] => Array
 *                         (
 *                             [1] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => CSI (Internet Explorer)
 *                                 )
 *
 *                             [2] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => CSI (Backup link - Don't have link)
 *                                 )
 *
 *                         )
 *
 *                     [1] => Array
 *                         (
 *                             [3] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => Customer view of Service Cloud
 *                                 )
 *
 *                             [4] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => Service Cloud
 *                                 )
 *
 *                         )
 *
 *                     [2] => Array
 *                         (
 *                             [5] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => Open to Suggestions
 *                                 )
 *
 *                         )
 *
 *                 )
 *
 *         )
 *
 *     [2] => Array
 *         (
 *             [categoryName] => Engineering
 *             [categoryType] => blue
 *             [links] => Array
 *                 (
 *                     [0] => Array
 *                         (
 *                             [6] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => Check Returned OPTs
 *                                 )
 *
 *                             [7] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => EMC Caller
 *                                 )
 *
 *                         )
 *
 *                     [1] => Array
 *                         (
 *                             [8] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => Eng Links
 *                                 )
 *
 *                             [9] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => Eng RCA Database
 *                                 )
 *
 *                         )
 *
 *                     [2] => Array
 *                         (
 *                             [10] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => OPT
 *                                 )
 *
 *                             [11] => Array
 *                                 (
 *                                     [linkUrl] => #
 *                                     [linkLinks] => Surge (Internet Explorer)
 *                                 )
 *
 *                         )
 *
 *                 )
 *
 *         )
 *
 * )
 */
$formattedData = [];

// The number of items (maximal 2) per each "links row" item.
$numberOfItemsPerLinksRow = 0;

// The index of a "links row" item.
$indexOfCurrentLinksRow = 0;

foreach ($data as $item) {
    // Extract category data.
    $categoryId = $item['categoryId'];
    $categoryName = $item['cat'];
    $categoryType = $item['type'];

    // Add a category item if not already exist, with the category id as the item's key.
    if (!array_key_exists($categoryId, $formattedData)) {
        $formattedData[$categoryId] = [];
        $formattedData[$categoryId]['categoryName'] = $categoryName;
        $formattedData[$categoryId]['categoryType'] = $categoryType;

        // In this category item add an array to hold the list of links items.
        $formattedData[$categoryId]['links'] = [];

        // Reset.
        $indexOfCurrentLinksRow = 0;

        // Reset.
        $numberOfItemsPerLinksRow = 0;
    }

    // Extract link data.
    $linkId = $item['linkId'];
    $linkUrl = $item['url'];
    $linkLinks = $item['links'];

    // Add the details of the link as an item in the "links row" item. Notice the link id as key.
    $formattedData[$categoryId]['links'][$indexOfCurrentLinksRow][$linkId] = [
        'linkUrl' => $linkUrl,
        'linkLinks' => $linkLinks,
    ];

    // Increment.
    $numberOfItemsPerLinksRow++;

    if ($numberOfItemsPerLinksRow % 2 === 0) {
        // Reset.
        $numberOfItemsPerLinksRow = 0;

        // Increment.
        $indexOfCurrentLinksRow++;
    }
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
        <meta charset="UTF-8" />
        <!-- The above 3 meta tags must come first in the head -->

        <title>Demo</title>

        <style type="text/css">
            body {
                margin: 0;
                padding: 20px;
                color: #333;
            }

            a {
                text-decoration: none;
            }

            .category {
                padding: 10px;
                margin-bottom: 10px;
                text-align: left;
                font-weight: 700;
                border: 1px solid #ccccff;
                background-color: #f3f8fd;
            }

            .links {
                width: 100%;
                border-spacing: 1px;
                border: 1px solid #fafafa;
            }

            .links td {
                width: 50%;
                padding: 10px;
                background-color: #fafafa;
            }

            .links td a {
                color: #0033ff;
                font-weight: 700;
                background-color: #f8f8f8;
            }

            .no-data {
                padding: 10px;
                margin-bottom: 10px;
                border: 1px solid #ddd;
                background-color: #f4f4f4;
            }
        </style>
    </head>
    <body>

        <h3>
            Categories
        </h3>

        <?php
        if ($formattedData) { // Data exists.
            foreach ($formattedData as $categoryId => $categoryItem) {
                ?>
                <p class="category">
                    <?php echo $categoryItem['categoryName']; ?>
                </p>

                <table class="links">
                    <?php
                    $links = $categoryItem['links'];

                    foreach ($links as $linksRow) {
                        ?>
                        <tr>
                            <?php
                            foreach ($linksRow as $linkId => $linkItem) {
                                ?>
                                <td>
                                    <a href="<?php echo $linkItem['linkUrl']; ?>">
                                        <?php echo $linkItem['linkLinks']; ?>
                                    </a>
                                </td>
                                <?php
                            }

                            // If there is no second link item in this "links row" item, then create an empty cell.
                            $numberOfItemsPerLinksRow = count($linksRow);
                            if ($numberOfItemsPerLinksRow === 1) {
                                ?>
                                <td>
                                    &nbsp;
                                </td>
                                <?php
                            }
                            ?>
                        </tr>
                        <?php
                    }
                    ?>
                </table>
                <?php
            }
        } else { // No data.
            ?>
            <p class="no-data">
                No data.
            </p>
            <?php
        }
        ?>

    </body>
</html>

db/connect.php:

Don't forget to change the database credentials.

<?php

// Db configs.
define('HOST', 'localhost');
define('PORT', 3306);
define('DATABASE', 'tests');
define('USERNAME', 'root');
define('PASSWORD', 'root');

/*
 * Enable internal report functions. This enables the exception handling,
 * e.g. mysqli will not throw PHP warnings anymore, but mysqli exceptions
 * (mysqli_sql_exception).
 *
 * MYSQLI_REPORT_ERROR: Report errors from mysqli function calls.
 * MYSQLI_REPORT_STRICT: Throw a mysqli_sql_exception for errors instead of warnings.
 *
 * @link http://php.net/manual/en/class.mysqli-driver.php
 * @link http://php.net/manual/en/mysqli-driver.report-mode.php
 * @link http://php.net/manual/en/mysqli.constants.php
 */
$mysqliDriver = new mysqli_driver();
$mysqliDriver->report_mode = (MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

/*
 * Create a new db connection.
 *
 * @see http://php.net/manual/en/mysqli.construct.php
 */
$db = new mysqli(HOST, USERNAME, PASSWORD, DATABASE, PORT);

Used definition and data for table "category":

CREATE TABLE `category` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `cat` varchar(100) DEFAULT NULL,
  `type` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `category` (`id`, `cat`, `type`)
VALUES
    (1, 'Platforms', 'red'),
    (2, 'Engineering', 'blue');

Used definition and data for table "links":

CREATE TABLE `links` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `links` varchar(100) DEFAULT NULL,
  `url` varchar(100) DEFAULT NULL,
  `catID` int(11) unsigned DEFAULT NULL,
  `type` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `catID` (`catID`),
  CONSTRAINT `links_ibfk_1` FOREIGN KEY (`catID`) REFERENCES `category` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

INSERT INTO `links` (`id`, `links`, `url`, `catID`, `type`)
VALUES
    (1, 'CSI (Internet Explorer)', '#', 1, NULL),
    (2, 'CSI (Backup link - Don\'t have link)', '#', 1, NULL),
    (3, 'Customer view of Service Cloud', '#', 1, NULL),
    (4, 'Service Cloud', '#', 1, NULL),
    (5, 'Open to Suggestions', '#', 1, NULL),
    (6, 'Check Returned OPTs', '#', 2, NULL),
    (7, 'EMC Caller', '#', 2, NULL),
    (8, 'Eng Links', '#', 2, NULL),
    (9, 'Eng RCA Database', '#', 2, NULL),
    (10, 'OPT', '#', 2, NULL),
    (11, 'Surge (Internet Explorer)', '#', 2, NULL);

Output:



来源:https://stackoverflow.com/questions/51459732/use-the-results-of-a-while-loop-in-another-while-loop

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!