Динамически добавлять столбцы к результатам запроса с помощью запросов CakePHP 3 ORM - PullRequest
2 голосов
/ 16 октября 2019

Я пытаюсь написать запрос, используя CakePHP 3.7 ORM, где нужно добавить столбец в набор результатов. Я знаю, что в MySQL такие вещи возможны: MySQL: динамически добавлять столбцы к результатам запроса

До сих пор я реализовал 2 пользовательских средств поиска . Первый выглядит следующим образом:

// src/Model/Table/SubstancesTable.php
public function findDistinctSubstancesByOrganisation(Query $query, array $options)
{
    $o_id = $options['o_id'];

    $query = $this
        ->find()
        ->select('id')
        ->distinct('id')
        ->contain('TblOrganisationSubstances')
        ->where([
            'TblOrganisationSubstances.o_id' => $o_id,
            'TblOrganisationSubstances.app_id IS NOT' => null
        ])
        ->orderAsc('Substances.app_id')
        ->enableHydration(false);

    return $query;
}

Второй пользовательский искатель:

// src/Model/Table/RevisionSubstancesTable.php
public function findProductNotifications(Query $query, array $options)
{
    $date_start = $options['date_start'];
    $date_end = $options['date_end'];

    $query = $this
        ->find()
        ->where([
            'RevisionSubstances.date >= ' => $date_start,
            'RevisionSubstances.date <= ' => $date_end
        ])
        ->contain('Substances')
        ->enableHydration(false);

    return $query;
}

Я использую искатели внутри контроллера, чтобы проверить его:

$Substances = TableRegistry::getTableLocator()->get('Substances');
$RevisionSubstances = TableRegistry::getTableLocator()->get('RevisionSubstances');

$dates = // method to get an array which has keys 'date_start' and 'date_end' used later.

$org_substances = $Substances->find('distinctSubstancesByOrganisation', ['o_id' => 123);

if (!$org_substances->isEmpty()) {

    $data = $RevisionSubstances
        ->find('productNotifications', [
            'date_start' => $dates['date_start'],
            'date_end' => $dates['date_end']
        ])
        ->where([
            'RevisionSubstances.substance_id IN' => $org_substances
        ])
        ->orderDesc('RevisionSubstances.date');

    debug($data->toArray());
}

Логика заключается в том, что я использую первый пользовательский искатель для создания объекта запроса, который содержит уникальные (DISTINCT в SQL) id поля из таблицы substances, основанные на конкретной компании (обозначается какполе o_id). Затем они передаются во второй пользовательский искатель путем реализации where(['RevisionSubstances.substance_id IN' ....

Это работает и дает мне все правильные данные. Пример вывода из оператора debug() выглядит следующим образом:

(int) 0 => [
    'id' => (int) 281369,
    'substance_id' => (int) 1,
    'date' => object(Cake\I18n\FrozenDate) {

        'time' => '2019-09-02T00:00:00+00:00',
        'timezone' => 'UTC',
        'fixedNowTime' => false

    },
    'comment' => 'foo',
    'substance' => [
        'id' => (int) 1,
        'app_id' => 'ID000001',
        'name' => 'bar',
        'date' => object(Cake\I18n\FrozenDate) {

            'time' => '2019-07-19T00:00:00+00:00',
            'timezone' => 'UTC',
            'fixedNowTime' => false

        }
    ]
],

Проблема, с которой я столкнулся, заключается в следующем: Каждый из возвращаемых результатов содержит app_idполе (['substance']['app_id'] в массиве выше). Что мне нужно сделать, это выполнить подсчет (COUNT() в MySQL) для другой таблицы на основе этого, а затем добавить это к результирующему набору.

Я не уверен, как это сделать для парыпричины. Во-первых, я понимаю, что пользовательские искатели возвращают объекты запроса , но запрос не выполняется в этот момент . Поскольку я не выполнил запрос - до вызова $data->toArray() - я не уверен, как бы я ссылался на app_id таким образом, чтобы на него можно было ссылаться на строку?

Эквивалентный SQL, который быдайте мне требуемые результаты:

SELECT COUNT (myalias.app_id) FROM (
    SELECT
        DISTINCT (tbl_item.i_id),
        tbl_item.i_name,
        tbl_item.i_code,
        tbl_organisation_substances.o_id,
        tbl_organisation_substances.o_sub_id,
        tbl_organisation_substances.app_id,
        tbl_organisation_substances.os_name
    FROM
        tbl_organisation_substances
    JOIN tbl_item_substances
        ON tbl_organisation_substances.o_sub_id = tbl_item_substances.o_sub_id 
    JOIN tbl_item
        ON tbl_item.i_id = tbl_item_substances.i_id
    WHERE
        tbl_item.o_id = 1
        AND
        tbl_item.date_valid_to IS NULL
        AND
        tbl_organisation_substances.app_id IS NOT NULL
    ORDER BY
        tbl_organisation_substances.app_id ASC
) AS myalias
WHERE myalias.app_id = 'ID000001'

Это COUNT(), где app_id равно ID000001.

Так что в массиве, который я дал ранее, мне нужнодобавить что-то в массив для хранения этого, например,

 'substance' => [
     // ...
 ],
 'count_app_ids' => 5

(при условии, что запросом было возвращено 5 строк).

У меня есть классы таблиц для всех упомянутых таблицв приведенном выше запросе.

Итак, мой вопрос: как вы пишете это, используя ORM, и добавляете результат обратно в набор результатов перед выполнением запроса?

Возможно ли это вообще? Единственное другое решение, которое я могу придумать, - записать данные (из того запроса, который у меня работает) во временную таблицу, а затем выполнить последовательные запросы, которые UPDATE с числом, основанным на app_id. Но я действительно не заинтересован в этом решении, потому что это может привести к огромным проблемам с производительностью. Кроме того, я хотел бы иметь возможность разбивать свой запрос на страницы, поэтому в идеале нужно, чтобы все было ограничено одним оператором SQL, даже если это выполняется несколькими поисковыми системами.

Я отметил это с помощью MySQL, а также CakePHP, потому чтоя даже не уверен, что это достижимо с точки зрения MySQL, хотя он и смотрит на связанный пост SO, как это можно сделать? Это создает дополнительную сложность необходимости писать эквивалентный запрос с использованием ORM Cake.

...