我有一个模型,文章,其中有很多摘要。我想加载10篇最新文章,并为每篇文章加载具有最高分数的摘要。我的功能如下:
public function getArticles($category, $viewName) {
$subArticles = $this->Articles->findByCategory($category)->contain([
'Abstracts' => function ($q) {
return $q
->select(['body', 'points', 'article_id'])
->where(['Abstracts.approved' => true])
->limit(10)
->order(['Abstracts.points' => 'DESC']);
}
])
->limit(10)
->order(['Articles.created' => 'DESC']) ;
$this->set( $viewName . 'Articles', $subArticles );
}
我得到的结果不是我想要的。通过SQL,首先CakePHP获取该类别中所有内容的articles.id(很好)。然后,CakePHP进入Abstracts表,使用那10篇文章。它刚刚找到,并要求获得最高票数的10篇摘要(属于那些文章)。
问题是我想为每篇文章提供1篇摘要,而不是属于该类别任何文章的10篇摘要。我怎样才能解决这个问题?谢谢!
修改
ndm建议这是Using limit() on contained model的副本,所以我尝试了那里的解决方案。也就是说,我将此添加到我的模型中:
$this->hasOne('TopAbstract', [
'className' => 'Abstracts',
'foreignKey' => 'abstract_id',
'strategy' => 'select',
'sort' => ['TopAbstract.points' => 'DESC'],
'conditions' => function ($e, $query) {
$query->limit(1);
return $e;
} ]);
然后我尝试使用contains([' TopAbstract'])查找文章byCategory,只有这会杀死我的SQL。它死于可怕的死亡:
Error: SQLSTATE[HY000]: General error: 1 near ")": syntax error
Debug甚至没有显示杀死它的查询,所以我不确定如何调试这个?
修改
稍微和自己说话,但错误肯定是在'条件' hasOne的一部分。我把它拿出来,它工作正常。无法找到一个关于如何看待互联网的例子......任何人都有任何想法?
答案 0 :(得分:25)
您正在寻找的是greatest-n-per-group问题的解决方案。您没有提及任何特定的RDBMS,但仍然可以看到 http://dev.mysql.com/doc/refman/5.6/en/example-maximum-column-group-row.html
所以让我们试一试,这里有三个可以应用于关联级别的选项(定义条件也可以移动到自定义查找器中),但是你可能会认为它们不是那个“直截了当”
对于某些特定的HasMany
,请一直向下滚动!
$this->hasOne('TopAbstracts', [
'className' => 'Abstracts',
'strategy' => 'select',
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$query->innerJoin(
[
'AbstractsFilter' => $query
->connection()
->newQuery()
->select(['article_id', 'points' => $query->func()->max('points')])
->from('abstracts')
->group('article_id')
],
[
'TopAbstracts.article_id = AbstractsFilter.article_id',
'TopAbstracts.points = AbstractsFilter.points'
]
);
return [];
}
]);
这将通过基于最大点的连接查询选择顶部摘要,它看起来像
SELECT
TopAbstracts.id AS `TopAbstracts__id`, ...
FROM
abstracts TopAbstracts
INNER JOIN (
SELECT
article_id, (MAX(points)) AS `points`
FROM
abstracts
GROUP BY
article_id
)
AbstractsFilter ON (
TopAbstracts.article_id = AbstractsFilter.article_id
AND
TopAbstracts.points = AbstractsFilter.points
)
WHERE
TopAbstracts.article_id in (1,2,3,4,5,6,7,8, ...)
$this->hasOne('TopAbstracts', [
'className' => 'Abstracts',
'strategy' => 'select',
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$query->leftJoin(
['AbstractsFilter' => 'abstracts'],
[
'TopAbstracts.article_id = AbstractsFilter.article_id',
'TopAbstracts.points < AbstractsFilter.points'
]);
return $exp->add(['AbstractsFilter.id IS NULL']);
}
]);
这将使用基于没有a.points < b.points
的行进行过滤的自联接,它看起来像
SELECT
TopAbstracts.id AS `TopAbstracts__id`, ...
FROM
abstracts TopAbstracts
LEFT JOIN
abstracts AbstractsFilter ON (
TopAbstracts.article_id = AbstractsFilter.article_id
AND
TopAbstracts.points < AbstractsFilter.points
)
WHERE
(AbstractsFilter.id IS NULL AND TopAbstracts.article_id in (1,2,3,4,5,6,7,8, ...))
$this->hasOne('TopAbstracts', [
'className' => 'Abstracts',
'foreignKey' => false,
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$subquery = $query
->connection()
->newQuery()
->select(['SubTopAbstracts.id'])
->from(['SubTopAbstracts' => 'abstracts'])
->where(['Articles.id = SubTopAbstracts.article_id'])
->order(['SubTopAbstracts.points' => 'DESC'])
->limit(1);
return $exp->add(['TopAbstracts.id' => $subquery]);
}
]);
这将使用相关子查询,该子查询使用具有简单排序的相当具体的选择,并限制选择最高注释。请注意,foreignKey
选项设置为false
,以避免将其他Articles.id = TopAbstracts.article_id
条件编译到连接条件中。
查询看起来像
SELECT
Articles.id AS `Articles__id`, ... ,
TopAbstracts.id AS `TopAbstracts__id`, ...
FROM
articles Articles
LEFT JOIN
abstracts TopAbstracts ON (
TopAbstracts.id = (
SELECT
SubTopAbstracts.id
FROM
abstracts SubTopAbstracts
WHERE
Articles.id = SubTopAbstracts.article_id
ORDER BY
SubTopAbstracts.points DESC
LIMIT
1
)
)
所有这3个选项都会查询并注入记录而没有任何hackery,它只是不是非常“直截了当”。
为了完整起见,当然总是可以手动加载关联的记录并适当地格式化结果,例如使用结果格式化程序,例如参见 CakePHP Entity contain without foreign key
仅供参考,我最初偶然发现了一个奇怪的解决方案。真的不应该使用这个!
这将选择所有相关的摘要,然后ORM将迭代它们,并且每篇文章选择具有匹配的article_id
值的第一个。所以从理论上讲,当在points
上订购时,ORM应该选择最多点的那个。
虽然我希望这个开箱即用,但似乎ORM以相反的顺序迭代结果,这将导致错误的行被选中。为了使其工作,查询需要使用通常需要使用的相反顺序,即ASC
而不是DESC
。
$this->hasOne('TopAbstracts', [
'className' => 'Abstracts',
'foreignKey' => 'abstract_id',
'strategy' => 'select',
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$query->order(['TopAbstracts.points' => 'ASC']);
return [];
}
]);
此函数还需要返回一个空数组而不是链接答案中显示的表达式,因为这会导致编译无效的SQL。这两种行为,反向顺序迭代和无效的SQL都可能是错误。
虽然这会起作用,但它总会选择所有相关的摘要,而不仅仅是那些可能被认为效率相当低的摘要,看起来像
SELECT
Articles.id AS `Articles__id`, ...
FROM
articles Articles
SELECT
TopAbstracts.id AS `TopAbstracts__id`, ...
FROM
abstracts TopAbstracts
WHERE
TopAbstracts.article_id in (1,2,3,4,5,6,7,8, ...)
ORDER BY
TopAbstracts.points ASC
我尝试了HasMany
个关联,但是我现在太忙了以进一步追求这个...只是根据ROW_NUMBER()
仿真类似,将MySQL特定的自定义关联放在一起进行测试到 MySQL select top X records for each individual in table 。
如果有人有兴趣,请查看 https://gist.github.com/ndm2/039da4009df1c5bf1c262583603f8298