Использование результатов одного запроса для создания другого запроса и заполнения выборки HTML

Изменить: переместит это наверх, чтобы избежать путаницы:

Я не имею права голоса или права принимать решения о структуре базы данных, поэтому я должен работать с тем, что у меня есть. В противном случае, как указано в большинстве комментариев, эта проблема будет значительно упрощена за счет нормализации и очистки таблиц.


У меня есть таблица базы данных, которая содержит модели автомобилей и некоторые «категории» (национальный автомобиль, импортный автомобиль, пикап, фургон и т. д.). Следует отметить, что эти категории представлены в целочисленных кодах.

В этой таблице есть 10 столбцов категорий для каждой строки (Использование1, Использование2, Использование3 и т. д.), поэтому у каждого автомобиля их может быть до 10.

Затем у меня есть еще одна таблица, содержащая описания, соответствующие каждому коду категории (например, национальный автомобиль = 1, импортный автомобиль = 2 и т. д.).

У меня есть форма, которая содержит два элемента выбора HTML: один для автомобилей, а другой для категорий.

<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>

Мне нужно заполнить выбор категории на основе значения выбора автомобилей. Это делается с помощью Ajax (часть, которая работает по назначению и, вероятно, не имеет отношения к теме).

Функция Ajax отправляет данные об автомобиле в php-скрипт, который успешно получает все соответствующие категории с помощью запроса.

С чем я борюсь, так это с использованием результатов этого запроса для получения описаний всех категорий, которые были получены из него с помощью другого запроса.

Я попытался выполнить foreach, чтобы перебрать все результаты запроса № 1, а затем выполнить запрос № 2, а затем сформулировать теги <option>, но я не понимаю это правильно.

<?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;
?>

Результирующие <option> элементы должны содержать код автомобиля в качестве значения и описание в качестве его «описания».

Прошу прощения, если объяснение слишком запутанное, если вам нужна дополнительная информация или разъяснения, пожалуйста, дайте мне знать.

Изменить: пример var_dump из результата первого запроса:

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" }

Скриншоты таблиц:

Таблица 1Таблица 2

Имейте в виду, что это фактические имена таблиц и столбцов, я изменил их в приведенном выше коде для иллюстрации.


person Community    schedule 29.11.2019    source источник
comment
Покажите массив, что вы получаете, я имею в виду $result на var_dump($result)   -  person Serghei Leonenco    schedule 29.11.2019
comment
Добавлен var_dump для $result.   -  person    schedule 29.11.2019
comment
"Эта таблица имеет 10 столбцов категорий для каждой строки (Использование1, Использование2..." Стоп! Почти наверняка ваша таблица нуждается в нормализации. Это подтверждается вашей попыткой обходного пути. Найдите в Интернете "нормализация базы данных" для получения дополнительной информации .   -  person Tim Morton    schedule 29.11.2019
comment
Как указано внизу, я не контролирую базу данных и должен работать с тем, что мне дано. В противном случае да, нормализация была бы лучшим вариантом здесь.   -  person    schedule 29.11.2019


Ответы (2)


Здесь много ошибок, большинство из которых можно выяснить, проверив ваши данные. Используйте var_dump и print_r, чтобы убедиться, что переменная содержит то, что, по вашему мнению, она делает.

Во-первых, если вы назначаете псевдоним столбцу базы данных, это всего лишь один столбец. Таким образом, вы получили индексы массива от CodUso1, CodUso2 и т. д. до CodUso9, а затем categories в качестве псевдонима для CodUso10. Во-вторых, вы пытаетесь перебрать $result["categories"], которого не существует, потому что fetchAll вернет индексированный массив. И то, и другое вы могли бы реализовать, используя print_r($result); и взглянув на то, с чем вы работаете. В-третьих, вы передаете пользовательские данные непосредственно в запрос к базе данных, что является хорошим способом атаки на вашу базу данных.

Попробуйте это. Мы безопасно подготавливаем оператор, который извлекает все десять столбцов категорий, а затем перебираем их по одному за раз, выполняя второй подготовленный оператор для получения результатов. Обратите внимание, что одним из преимуществ подготовленных операторов, помимо безопасности, является снижение накладных расходов при многократном выполнении одного и того же запроса. Наконец, разделите код и презентацию настолько, насколько это возможно. Выводить HTML на экран в середине цикла некрасиво, и с ним сложнее работать. Вы должны вернуть объект JSON, а затем использовать обратный вызов Javascript для создания элементов.

<?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);

Затем в обратном вызове Javascript сделайте что-то вроде этого. Я предполагаю, что вы используете 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);
    });
}
person miken32    schedule 29.11.2019

Причина, по которой foreach не работает, заключается в том, что $result['categories'] не является массивом. Предложение select вашего запроса просто переименовывает CodUse10 в categories.

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

Но реальная проблема гораздо глубже. Это восходит к вашей схеме таблицы. Каждый раз, когда у вас есть такие вещи, как field1, field2, field3, ... у вас почти наверняка есть таблица, которая не нормализована. Правильная схема будет выглядеть примерно так:

Car    Car_Category   Category 
id     car_id         id 
model  category_id    description

Если вы хотите получить список категорий определенного автомобиля, просто запросите объединенные таблицы:

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=?

Я не совсем понимаю, пытались ли вы отобразить доступные параметры или выбранные параметры, но в любом случае будет использоваться одно и то же соединение «многие ко многим». Таким образом, вместо car_category у вас может быть таблица с именем available_options и другая с именем selected_options.


...Но я не могу изменить базу данных!

В этом случае не будет «правильного» решения; просто некоторые творческие способы обойти это. Цель должна состоять в том, чтобы свести к минимуму сумму созданного технического долга. Вероятно, самая большая часть — отделить модель (логику) от представления (html). Таким образом, если/когда схема исправлена, вам не нужно копаться в представлении и ломать что-то при изменении методов доступа; все, что вам нужно сделать, это подключить другой класс или функцию, которая получает информацию и выводит ее таким же образом.

Определите, как представление ожидает получать данные модели

Прежде чем я начну работу над моделью, мне нужно знать, как представление будет работать с ней. Для моих собственных приложений у меня есть класс, который форматирует ввод, выпадающие списки и флажки для меня, аля 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>

Чтобы использовать это, я бы предоставил ассоциативный массив, где options = array('value'=>'description'). Дело в том, что мне нужна модель для предоставления этого массива, однако она может получать информацию. Но модель не отвечает за создание какого-либо HTML-кода.

Используя тот же план, ваш результат может выглядеть так:

<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>

Создайте модель для предоставления этих данных

Обычно я бы использовал объект, поскольку я использую свою собственную структуру MVC. Но для простоты я буду использовать обычную старую функцию (но я настоятельно рекомендую использовать классы, поскольку они гораздо более гибкие, а имена методов находятся в области видимости класса, а не в глобальной области видимости)

Найти описание по одному

Вы хотите прокрутить результаты 10 различных полей, все с удобным названием CodUso{$number}.

Сначала создайте функцию для получения необработанных данных.

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);
 }      

Теперь создайте функцию для получения описаний категорий.

// 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();
}

Затем создайте функцию для перебора доступных вариантов:

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

Вау! теперь осталось только использовать его в представлении:

<?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>

Ура! Я нормализовал таблицы!

Если вы нормализуете таблицы, просто напишите новую функцию, чтобы заменить текущую функцию getCategoriesFor() (при необходимости замените имена реальных полей) и используйте ее в представлении вместо getCategoryOptionsByModel($car_model), или просто измените getCategoryOptionsByModel($car_model), чтобы она возвращала 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;
}

Преимущества

Отделив логику от представления и убедившись, что каждая функция выполняет одну небольшую функцию, вы упростили изменение способа компиляции данных. Вам не нужно вносить какие-либо изменения в представление, кроме как указать другой источник данных. Если следующий программист после вас будет маньяком-убийцей, который знает, где вы живете, вы будете в большей безопасности!

Отказ от ответственности

Это все не в моей голове. Хотя я рекомендую PDO, я не использую ни MySQL, ни PDO, и мое использование может нуждаться в корректировке.

person Tim Morton    schedule 29.11.2019
comment
Я действительно пытаюсь получить доступные варианты категорий для выбранного в данный момент автомобиля. О нормализации таблиц не может быть и речи, так как я, к сожалению, не контролирую БД. Как внутреннее соединение будет работать с 10 различными идентификаторами категорий? - person ; 29.11.2019
comment
Это прискорбно... Вы будете вынуждены писать хакерский код, который сделает ваш код частью технического долга, когда кто-то настолько отчаялся, чтобы его исправить. Я вернусь и добавлю раздел «Что делать, если вы не можете исправить базу данных», когда я смогу вернуться к этому. - person Tim Morton; 29.11.2019
comment
... Я совершенно пропустил, что это был вызов ajax. Определенно json был бы подходящим способом. Я просто заполнил параметры для начальной страницы. - person Tim Morton; 02.12.2019
comment
Не поймите меня неправильно, я нашел другой ответ более подходящим для того, что я искал (частично из-за использования объекта json и jquery для заполнения выбора вместо php), но я принял во внимание оба ответа при создании окончательного сценария (например, были некоторые проблемы с битом jquery в принятом ответе, ваша структура запроса казалась более соответствующей моей текущей кодовой базе и т. д.). Кроме того, ваше подробное объяснение по этому вопросу было очень кстати. Спасибо! - person ; 02.12.2019
comment
Просто смущен тем, что пропустил ссылку на ajax;) не беспокойтесь, спасибо за голосование; Рад, что смог помочь - person Tim Morton; 02.12.2019