Cake PHP ORM: Запрос с объединением на одной таблице с нестандартной ассоциацией - PullRequest
0 голосов
/ 23 апреля 2020

У меня особый случай использования, и я не могу найти чистое решение с ORM. Я много искал, и, возможно, моя модель базы данных неверна, я не уверен.

Я использую Cake PHP 3.8.11.

Итак, у меня есть таблица «MaintenanceTypes» с 3 важными полями: идентификатор, имя и периодичность. Периодичность (в днях) означает, что «это обслуживание должно выполняться каждые (например) 30 дней».

Периодичность равна 7 (неделя), 30 (месяц), 90 (триместр) и т. Д.

У меня также есть таблица «Операции», это небольшие юнит-тесты, которые относятся к «MaintenanceType» (поля: id, name, maintenance_type_id).

Что особенного в этом случае, это что, как бизнес-правило, Операции, принадлежащие MaintenanceType с периодичностью 7 дней, «включаются» в каждый MaintenanceType с большей периодичностью; это означает, что в каждом триместре вы должны выполнять все операции, связанные непосредственно с триместром, , но также все операции, связанные с месяцем и неделей, и т. д. c.

В необработанном виде SQL это тривиально: small_smile:

mt_ref является ссылочным значением MaintenanceType, mt_in c является включенными типами обслуживания (с меньшей периодичностью) и, наконец, каждая операция принадлежность к любому из найденных MaintenanceTypes.

    SELECT mt_ref.id, mt_ref.name, mt_ref.periodicity, 
        mt_inc.name, mt_inc.periodicity, o.name 
    FROM maintenance_types mt_ref 
        LEFT JOIN maintenance_types mt_inc 
            ON (mt_inc.periodicity <= mt_ref.periodicity) 
        LEFT JOIN operations o ON (o.maintenance_type_id = mt_inc.id) 
    WHERE mt_ref.id = 3 

Я пытался объявить связь между MaintenanceTypes, но не могу найти способ объявить, что связь сделана в поле периодичности, и, дополнительные баллы, не на равенство, а на «меньше или равно».

Чтобы добавить дополнительные трудности, я использую этот запрос для (очень хорошего) JQuery Datatables Cake PHP плагина ( https://github.com/allanmcarvalho/cakephp-datatables), поэтому я не могу просто передать необработанный SQL, и я должен использовать ORM ...

Я надеюсь, что это понятно, и что кто-то может помочь мне в этом один!

че много anks!

1 Ответ

2 голосов
/ 23 апреля 2020

Если вам нужны экземпляры построителя запросов, то здесь в значительной степени есть два варианта, которые являются более или менее простыми, то есть либо использовать связи с пользовательскими условиями, либо ручные объединения.

Пользовательские условия ассоциации

Со связями вы, вероятно, будете делать что-то вроде самоассоциирования с MaintenanceTypes с отключенным внешним ключом и пользовательскими условиями, как в вашем MaintenanceTypesTable классе:

$this
    ->hasMany('MaintenanceTypesInc')
    ->setClassName('MaintenanceTypes')
    ->setForeignKey(false)
    ->setConditions(function (
        \Cake\Database\Expression\QueryExpression $exp,
        \Cake\ORM\Query $query
    ) {
        return $exp->lte(
            $query->identifier('MaintenanceTypesInc.periodicity'),
            $query->identifier('MaintenanceTypes.periodicity')
        );
    });

Отключение внешнего ключа не позволит ORM создать условие A.fk = B.fk по умолчанию при присоединении к ассоциации. Следует отметить, что вы не можете содержать hasMany связь с отключенным внешним ключом, вы можете только присоединиться к ней! Вместо этого вы могли бы использовать hasOne или даже belongsTo связь, но это было бы вроде ie, поскольку у вас нет здесь отношения 1: 1 (по крайней мере, насколько я понимаю).

Также обратите внимание, что вам необязательно использовать обратный вызов со всеми выражениями для условий, вы можете передать условия как массив key => value с созданным вручную выражением идентификатора или даже в виде простой строки (последняя однако не будет распознаваться при использовании автоматического c цитирования идентификатора):

->setConditions([
    'MaintenanceTypesInc.periodicity <=' =>
        new \Cake\Database\Expression\IdentifierExpression('MaintenanceTypes.periodicity');
]);
->setConditions('MaintenanceTypesInc.periodicity <= MaintenanceTypes.periodicity');

Если у вас также есть ассоциация для Operations в вашем классе MaintenanceTypesTable, вы сможете присоединиться как в новой, так и в ассоциации Operations с помощью методов построения запросов *JoinWith(), например:

$query = $maintenanceTypesTable
    ->find()
    ->select([
        'MaintenanceTypes.id', 'MaintenanceTypes.name', 'MaintenanceTypes.periodicity',
        'MaintenanceTypesInc.name', 'MaintenanceTypesInc.periodicity',
        'Operations.name',
    ])
    ->leftJoinWith('MaintenanceTypesInc.Operations');

В результате данные ассоциации будут помещены под ключ _matchingData, ie вы можете получить его как $entity->_matchingData->MaintenanceTypesInc и $entity->_matchingData->Operations. Если вы не хотите этого, вам нужно использовать псевдонимы для полей ассоциаций, например:

->select([
    'MaintenanceTypes.id', 'MaintenanceTypes.name', 'MaintenanceTypes.periodicity',
    'mt_inc_name' => 'MaintenanceTypesInc.name', 'mt_inc_periodicity' => 'MaintenanceTypesInc.periodicity',
    'op_name' => 'Operations.name',
])

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

ручные объединения

Использование ручных объединений дает вам полную свободу, с помощью методов построения запросов *Join() вы можете создавать любые объединения, которые вам нравятся, а вам нет необходимо использовать возможные обходные пути с ассоциациями.

Вы можете добавить их в пользовательском искателе для правильного повторного использования, это может выглядеть примерно так в вашем MaintenanceTypesTable классе:

public function findWithIncludedMaintenanceTypes(\Cake\ORM\Query $query, array $options)
{
    return $query
        ->select(/* ... */)
        ->leftJoin(
            ['MaintenanceTypesInc' => 'maintenance_types'],
            function (
                \Cake\Database\Expression\QueryExpression $exp,
                \Cake\ORM\Query $query
            ) {
                return $exp->lte(
                    $query->identifier('MaintenanceTypesInc.periodicity'),
                    $query->identifier('MaintenanceTypes.periodicity')
                );
            }
        )
        ->leftJoin(
            ['Operations' => 'operations'],
            function (
                \Cake\Database\Expression\QueryExpression $exp,
                \Cake\ORM\Query $query
            ) {
                return $exp->equalFields(
                    'Operations.maintenance_type_id ',
                    'MaintenanceTypesInc.id'
                );
            }
        );
}

Тогда вы просто используйте искатель везде, где вам это нужно, например:

$query = $maintenanceTypesTable
    ->find('withIncludedMaintenanceTypes');

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

См. также

...