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');
请注意,就像在关联示例中一样,您也可以为自定义联接使用字符串或数组条件。
另见
- Cookbook > Database Access & ORM > Associations - Linking Tables Together
- Cookbook > Database Access & ORM > Query Builder > Loading Associations
- Cookbook > Database Access & ORM > Query Builder > Loading Associations > Using leftJoinWith
- Cookbook > Database Access & ORM > Query Builder > Loading Associations > Adding Joins
- Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Using Finders to Load Data
- Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods
我有一个非常特殊的用例,我找不到一个干净的 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');
请注意,就像在关联示例中一样,您也可以为自定义联接使用字符串或数组条件。
另见
- Cookbook > Database Access & ORM > Associations - Linking Tables Together
- Cookbook > Database Access & ORM > Query Builder > Loading Associations
- Cookbook > Database Access & ORM > Query Builder > Loading Associations > Using leftJoinWith
- Cookbook > Database Access & ORM > Query Builder > Loading Associations > Adding Joins
- Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Using Finders to Load Data
- Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods