CakePHP ORM:在具有非标准关联的相同 table 上使用连接进行查询

CakePHP ORM : Query with a join on the same table with a non-standard association

我有一个非常特殊的用例,我找不到一个干净的 ORM 解决方案。查了很多,可能是我的数据库模型不对,我也不确定。

我使用 CakePHP 3.8.11。

所以,我有一个 table "MaintenanceTypes",其中包含 3 个重要字段:id、名称和周期性。周期性(以天为单位)表示 "this maintenance is to be done every (for instance) 30 days".

周期如 7(周)、30(月)、90(三个月)等。

我还有一个table "Operations",它们是属于"MaintenanceType"的小单元测试(字段是id,name,maintenance_type_id)。

这种情况的特殊之处在于,作为业务规则,属于周期为 7 天的 MaintenanceType 的操作在 every MaintenanceType 中是 "included"具有更大的周期性;这意味着每个三个月,您应该执行与该三个月直接相关的每个操作,但是还有与月、周等相关的每个操作

在原始 SQL 中很简单 :slight_smile:

mt_ref 是参考维护类型,mt_inc 是包含的维护类型(周期性较小)最后,找到属于任何维护类型的每个操作。

    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 之间的关联,但我找不到一种方法来声明关联是在周期性字段上完成的,而且,额外的一点,不是在相等性上,而是在 "less or equal".

为了增加额外的困难,我将此查询用于(非常好)JQuery Datatables CakePHP 插件(https://github.com/allanmcarvalho/cakephp-datatables),所以我不能简单地通过原始 SQL,我必须使用 ORM...

我希望这是清楚的,并且有人可以在这方面帮助我!

非常感谢!

如果您需要查询构建器实例,那么这里几乎有两个或多或少简单的选项,即使用与自定义条件的关联,或手动连接。

自定义关联条件

对于关联,您可能会使用禁用的外键和自定义条件与 MaintenanceTypes 进行自我关联,就像在您的 MaintenanceTypesTable class 中一样:

$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 关联,但这有点像个谎言,因为您在这里没有 1:1 关系(至少据我所知).

另请注意,您不必对条件的所有表达式使用回调,您可以将条件作为带有手动实例化标识符表达式的 key => value 数组传递,甚至可以作为简单的字符串传递(然而,当使用自动标识符引用时,后者将不会被识别):

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

假设您在 MaintenanceTypesTable class 中也有 Operations 的协会,您应该能够同时加入新协会和 Operations 协会通过查询构建器 *JoinWith() 方法,如下所示:

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

在结果中,关联数据会放在_matchingData键下,即你可以像$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',
])

如果您不想每次都 select 所有字段,请使用自定义查找器,如以下手动连接示例所示。

手动加入

使用手动联接为您提供了完全的自由,使用查询构建器 *Join() 方法,您可以创建任何您喜欢的联接,并且您不必使用关联的可能变通方法。

您可以将它们添加到自定义查找器中以实现适当的可重用性,它在您的 MaintenanceTypesTable class:

中可能看起来像这样
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');

请注意,就像在关联示例中一样,您也可以为自定义联接使用字符串或数组条件。

另见