我想动态更改关联策略(hasMany)到"in"(默认)到"选择"。因为这将纠正这种情况的结果:
">获取所有出版商,只获取前五本书":
$publishersTable = TableRegistry::getTableLocator()->get('Publishers');
$publishersTable->getAssociation('Books')->setStrategy('select');
$query = $publishersTable->find()
->contain(['Books'=> function(Query $q){
return $q->limit(5);
}]);
不幸的是,Cake 仍然使用"in"来运行查询而不是"分离查询",结果只有 5 个出版商(而不是所有拥有前 5 本书的出版商)。
是否可以即时更改策略? 提前感谢!
hasMany
关联将始终使用单个单独的查询,而不是多个单独的查询。select
策略和subquery
策略之间的区别在于,一个策略将直接与主键数组进行比较,另一个策略将与将与所选父记录匹配的联接子查询进行比较。
您正在尝试的是选择每个组的最大n个,这在内置关联加载器中是不可能的,并且根据您使用的DBMS可能会有点棘手,例如检查如何限制每个记录/组包含的关联?对于使用自定义关联和加载器的MySQL <8.x的示例。
对于支持它的 DBMS,请查看窗口函数。这是一个使用本机窗口函数的加载器示例,应该可以简单地将链接示例中的加载器替换为它,但请记住,它并没有真正经过测试或任何东西,我只是从一些实验中得到了它:
namespace AppORMAssociationLoader;
use CakeDatabaseExpressionOrderByExpression;
use CakeORMAssociationLoaderSelectLoader;
class GroupLimitedSelectLoader extends SelectLoader
{
/**
* The group limit.
*
* @var int
*/
protected $limit;
/**
* The target table.
*
* @var CakeORMTable
*/
protected $target;
/**
* {@inheritdoc}
*/
public function __construct(array $options)
{
parent::__construct($options);
$this->limit = $options['limit'];
$this->target = $options['target'];
}
/**
* {@inheritdoc}
*/
protected function _defaultOptions()
{
return parent::_defaultOptions() + [
'limit' => $this->limit,
];
}
/**
* {@inheritdoc}
*/
protected function _buildQuery($options)
{
$key = $this->_linkField($options);
$keys = (array)$key;
$filter = $options['keys'];
$finder = $this->finder;
if (!isset($options['fields'])) {
$options['fields'] = [];
}
/* @var CakeORMQuery $query */
$query = $finder();
if (isset($options['finder'])) {
list($finderName, $opts) = $this->_extractFinder($options['finder']);
$query = $query->find($finderName, $opts);
}
$rowNumberParts = ['ROW_NUMBER() OVER (PARTITION BY'];
for ($i = 0; $i < count($keys); $i ++) {
$rowNumberParts[] = $query->identifier($keys[$i]);
if ($i < count($keys) - 1) {
$rowNumberParts[] = ',';
}
}
$rowNumberParts[] = new OrderByExpression($options['sort']);
$rowNumberParts[] = ')';
$rowNumberField = $query
->newExpr()
->add($rowNumberParts)
->setConjunction('');
$rowNumberSubQuery = $this->target
->query()
->select(['__row_number' => $rowNumberField])
->where($options['conditions']);
$columns = $this->target->getSchema()->columns();
$rowNumberSubQuery->select(array_combine($columns, $columns));
$rowNumberSubQuery = $this->_addFilteringCondition($rowNumberSubQuery, $key, $filter);
$fetchQuery = $query
->select($options['fields'])
->from([$this->targetAlias => $rowNumberSubQuery])
->where([$this->targetAlias . '.__row_number <=' => $options['limit']])
->eagerLoaded(true)
->enableHydration($options['query']->isHydrationEnabled());
if (!empty($options['contain'])) {
$fetchQuery->contain($options['contain']);
}
if (!empty($options['queryBuilder'])) {
$fetchQuery = $options['queryBuilder']($fetchQuery);
}
$this->_assertFieldsPresent($fetchQuery, $keys);
return $fetchQuery;
}
}
谢谢@ndm但我找到了另一个更短的解决方案:
$publishersTable->find()
->formatResults(function ($results) use ($publishersTable) {
return $results->map(function ($row) use ($publishersTable) {
$row['books'] = $publishersTable->Books->find()
->where(['publisher_id'=>$row['id']])
->limit(5)
->toArray();
return $row;
});
});