简体   繁体   中英

Using the results of one query to make another query and populate an HTML select

Edit: will move this to the top to avoid confusion:

I have no say or power of decision on the database's structure, so I must work with what I've got. Otherwise, as most comments stated this issue would be simplified greatly by normalizing and cleaning up the tables.


I've got a database table which contains car models and some "categories" (national car, imported car, pick-up truck, van, etc). Something of note is that these categories are represented in integer codes.

This table has 10 category columns for each row (Use1, Use2, Use3, etc), so each car can have up to 10 of them.

Then I have another table which contains the descriptions corresponding to each category code (eg national car = 1, imported car = 2, and so on).

I have a form which contains two HTML select elements, one for cars and the other for categories.

<div class="form-group row">
    <label class="col-sm-2 form-control-label">Cars</label>
    <div class="col-sm-6">
        <select class="form-control" id="car_model" name="car_model" onchange="fetch_categories(this.value);">
            <option>Car 1</option>
            <option>Car 2</option>
            <option>Car 3</option>
        </select>
    </div>
</div>

<div class="form-group row">
    <label class="col-sm-2 form-control-label">Category</label>
    <div class="col-sm-4">
        <select class="form-control" name="category">
            <option>Select a car first...</option>
        </select>
    </div>
</div>

I need to populate the category select based on the value of the cars select. This is being done by using Ajax (part which works as intended and is probably irrelevant to the subject).

The Ajax function sends the car data to a php script which successfully gets all of it's corresponding categories through a query.

What I'm struggling with is using the results from that query to fetch the descriptions of all the categories that came from it through another query.

I've tried doing a foreach to iterate all results from query #1 and then executing query #2 then formulating the <option> tags, but I'm not getting it right.

<?php
$car_model = $_POST['get_car'];

$query = "SELECT CodUso1, 
                CodUso2, 
                CodUso3, 
                CodUso4, 
                CodUso5, 
                CodUso6, 
                CodUso7, 
                CodUso8, 
                CodUso9, 
                CodUso10 AS categories FROM cars WHERE car_model = '$car_model'";
$stmt = $pdo->prepare($query);
$stmt->execute();
$result = $stmt->fetchAll();

foreach ($result['categories'] as $u):
    if ($result['CodUso1'] != 0 || 
        $result['CodUso2'] != 0 || 
        $result['CodUso3'] != 0 || 
        $result['CodUso4'] != 0 || 
        $result['CodUso5'] != 0 || 
        $result['CodUso6'] != 0 || 
        $result['CodUso7'] != 0 || 
        $result['CodUso8'] != 0 || 
        $result['CodUso9'] != 0 || 
        $result['CodUso10'] != 0) {
        $query = "SELECT description FROM categories WHERE code = '$u'";
        $stmt = $pdo->prepare($query);
        $stmt->execute();
        $result2 = $stmt->fetchAll();
?>      
        <option value="<?php echo $u ?>">
        <?php echo $result2; ?>
        </option>
<?php
}
endforeach;
exit;
?>

The resulting <option> elements must contain the car code as value and the description as it's "description".

I apologize if the explanation is too convoluted, if you need any further information or clarification please let me know.

Edit: example of var_dump from the first query's result:

array(20) { ["CodUso1"]=> string(3) "101" [0]=> string(3) "101" ["CodUso2"]=> string(3) "502" [1]=> string(3) "502" ["CodUso3"]=> string(3) "305" [2]=> string(3) "305" ["CodUso4"]=> string(3) "406" [3]=> string(3) "406" ["CodUso5"]=> string(3) "103" [4]=> string(3) "103" ["CodUso6"]=> string(3) "508" [5]=> string(3) "508" ["CodUso7"]=> string(3) "455" [6]=> string(3) "455" ["CodUso8"]=> string(1) "0" [7]=> string(1) "0" ["CodUso9"]=> string(1) "0" [8]=> string(1) "0" ["coduso"]=> string(1) "0" [9]=> string(1) "0" }

Screenshots of tables:

表格1表 2

Keep in mind these are the actual names of the tables and columns, I changed them in the above code for illustration purposes.

The reason the foreach is not working is because $result['categories'] is not an array. Your query's select clause simply renames CodUse10 to categories .

SELECT CodUso1, 
            CodUso2, 
            CodUso3, 
            CodUso4, 
            CodUso5, 
            CodUso6, 
            CodUso7, 
            CodUso8, 
            CodUso9, 
 CodUso10 AS categories

But the real issue goes deeper than that. It goes back to your table schema. Any time you have things like field1, field2, field3, ... you almost certainly have a table that is not normalized. The proper schema would look something like this:

Car    Car_Category   Category 
id     car_id         id 
model  category_id    description

When you want to get a list of categories a certain car has, simply query the joined tables:

select c.model, cat.id, cat.description
from car c
inner join car_category cc on c.id=cc.car_id
inner join category cat on cc.category_id=cat.id
where car.id=?

I don't completely understand if you were trying to display available options or selected options, but either way, the same kind of many-to-many join would be used. So instead of car_category you might have a table called available_options and another called selected_options .


...But I can't change the database!

In this case, there will be no "right" solution; just some creative ways to work around it. The goal should be to minimize the amount of technical debt created. Probably the biggest part is to separate model (logic) from the view (html). That way, if/when the schema is corrected, you don't have to dig into the view and break things as you change access methods; all you have to do is plug in a different class or function that gets the information and outputs it the same way.

Define how the view expects to receive the model's data

Before I start in on the model, I need to know how the view will work with it. For my own applications, I have a class which formats inputs, dropdowns, and checkboxes for me, ala ruby on rails:

<div class="form-group">
  <label for="category">Category</label>
  <?= FormDecorator::showAsDropDown($optionList, $defaultOption,['id'=>'category','name'=>'category','class'=>'form-control', etc...]) ?>
</div>

To use this, I would provide an associative array where options = array('value'=>'description'). The point is that I need the model to provide that array, however it may go about getting the information. But the model is not responsible for creating any html.

Using this same plan, your output could look like

<div class="form-group row">
    <label class="col-sm-2 form-control-label">Category</label>
    <div class="col-sm-4">
        <select class="form-control" name="category">
            <option disabled selected hidden>Select a car first...</option>
            <?php foreach($options as $value => $description): ?>
              <option value="<?= $value ?>"><?= $description ?></option>
            <?php endforeach; ?>
        </select>
    </div>
</div>

Create a model to provide that data

Normally, I would use an object since I use my own MVC framework. But for simplicity, I'll use a plain old function (but I highly recommend using classes as they are much more flexible, and method names are in the class's scope, not global scope)

Find description one at a time

You want to loop through the results of 10 different fields, all conveniently named CodUso{$number} .

First, make a function to retrieve the raw data:

function getCategoriesFor($car_model) {
    global $pdo; // ick, a class would avoid globals.
    $query =<<<CATEGORYQUERY
SELECT CodUso1, CodUso2, CodUso3, CodUso4, CodUso5, 
       CodUso6, CodUso7, CodUso8, CodUso9, CodUso10
FROM cars 
WHERE car_model = ?

CATEGORYQUERY;
    $stmt = $pdo->prepare($query);
    $stmt->execute($car_model);
    return stmt->fetch(\PDO::FETCH_ASSOC);
 }      

Now, make a function to get descriptions of the categories:

// if this were an object, I would call it $Category->find($id)
function getCategoryDescriptionOf($code) {
    global $pdo;
    $query = "select * from categories where code=?";
    $stmt = $pdo->prepare($query);
    $stmt->execute($id);
    return $stmt->fetchAll();
}

Then make a function to iterate through the options available:

function getCategoryOptionsByModel($car_model) {

    // get the row containing CodUso1 ... CodUso10
    $categoryRow = getCategoriesFor($car_model);

    // always initialize output before generating its contents
    $out = [];

    // 10 fields, iterate through them.  This is the hacky part...
    for($i=1; $i <= 10; $i++) {

        // generate the field name
        $field = 'CodUso' . $i;

        // get the code from the car_model table row 
        $code = $categoryRow[$field];

        // format the array we will use in the view
        $out[$code] = getCategoryDescriptionOf($code);
    }
    return $out;  // [code => description]
}

Whew! now all that's left is to use it in the view:

<?php
$car_model = $_GET['car_model']; // or however it is assigned...

?>
<html> 
  ... snip ...

<div class="form-group row">
    <label class="col-sm-2 form-control-label">Category</label>
    <div class="col-sm-4">
        <select class="form-control" name="category">
            <option disabled selected hidden>Select a car first...</option>
            <?php foreach( getCategoryOptionsByModel($car_model) as $value => $description): ?>
              <option value="<?= $value ?>"><?= $description ?></option>
            <?php endforeach; ?>
        </select>
    </div>
</div>

Hurray! I normalized the tables!

If you normalize the tables, just write a new function to replace the current getCategoriesFor() function (substitute real field names as necessary) and use it in the view instead of getCategoryOptionsByModel($car_model) or just change getCategoryOptionsByModel($car_model) to return getCategoriesFor($car_model):

function getCategoriesFor($car_model) {
    global $pdo;
    $query =<<<THISISHOWITSHOULDBEDONEQUERY

select cat.code, cat.description
from cars c
inner join car_category cc on c.id=cc.car_id
inner join category cat on cc.category_id=cat.id
where cars.car_model=?

THISISHOWITSHOULDBEDONEQUERY;

    $stmt = $pdo->prepare($query)->execute($car_model);
    $out = [];
    foreach($stmt as $row) {
        $code = $row['code'];
        $description = $row['description'];
        $out[$code] = $description;
    }
    return $out;
}

Benefits

By separating logic from presentation, and making sure each function does one small thing, you've now made it easier to change how the data is compiled. You shouldn't have to make any changes to the view, other than to name a different data source. If the next programmer after you is a homicidal maniac who knows where you live, you'll be a lot safer!

Disclaimer

This is all off the top of my head. Although I recommend PDO, I don't use MySQL nor PDO and my usage may need to be corrected.

Many mistakes here, most of which could be figured out by inspecting your data. Make use of var_dump and print_r to ensure that a variable contains what you think it does.

First, if you assign an alias to a database column, it's just one column. So you were ending up with array indices CodUso1 , CodUso2 , etc through to CodUso9 , and then categories as an alias to CodUso10 . Second, you try looping through $result["categories"] which doesn't exist, because fetchAll will return an indexed array. Both these you could have realized by using print_r($result); and taking a look at what you're working with. Third, you're passing user-supplied data directly into the database query, which is a good way to get your database attacked.

Try this out. We safely prepare a statement that retrieves all ten category columns, and then loop through them one at a time, executing a second prepared statement to get the results. Note one of the benefits of prepared statements, aside from security, is reduced overhead when repeatedly executing the same query. Finally, separate your code and your presentation as much as you can. Dumping HTML to screen in the middle of a loop is ugly and harder to work with. You should be returning a JSON object and then using your Javascript callback to build the elements.

<?php
$car_model = $_POST['get_car'];

// prepare the first query    
$query = "SELECT CodUso1, CodUso2, CodUso3, CodUso4, CodUso5,
        CodUso6, CodUso7, CodUso8, CodUso9, CodUso10
    FROM cars WHERE car_model = ? LIMIT 1";
$stmt = $pdo->prepare($query);
$stmt->execute([$car_model]);
// there's only one item, no loop needed
$categories = $stmt->fetch(\PDO::FETCH_ASSOC);

// here we prepare the second query
$query = "SELECT description FROM categories WHERE code = ?";
$stmt = $pdo->prepare($query);
// bind the variable to the ? parameter
$stmt->bindParam(1, $c);

$options = [];
foreach ($categories as $c) {
    if ($c == 0) {
        continue;
    }
    $stmt->execute();
    // we can fetch the single column directly
    $description = $stmt->fetchColumn();
    $options[] = ["id"=>$c, "description"=>$description];
}

header("Content-Type: application/json");
echo json_encode($options);

Then in your Javascript callback, do something like this. I'm assuming you're using jQuery:

...
success: function(data, status, xhr) {
    // erase existing values
    $("select[name='category']").html("");
    // jQuery will turn this into an object automatically
    // loop through it and append an option element for each item
    data.each(function(v) {
        var sel = $("<option>").val(v.id).html(v.description);
        $("select[name='category']").append(sel);
    });
}

@kurisu please share screenshots(or structure) of those two tables. it looks like you have to JOIN the tables when selecting.

Your upper query is wrong as I detected. What are CodUso values? Explain further. In your upper query, categories will get values only from CodUso10.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM