我想在CakePHP 3.0.0中对联合查询进行分页。通过使用custom finder,我几乎完美地工作,但我找不到任何方法让limit
和offset
适用于联合,而不是任何一个子查询。
换句话说,这段代码:
$articlesQuery = $articles->find('all');
$commentsQuery = $comments->find('all');
$unionQuery = $articlesQuery->unionAll($commentsQuery);
$unionQuery->limit(7)->offset(7); // nevermind the weirdness of applying this manually
产生这个查询:
(SELECT {article stuff} ORDER BY created DESC LIMIT 7 OFFSET 7)
UNION ALL
(SELECT {comment stuff})
而不是我想要的,这是:
(SELECT {article stuff})
UNION ALL
(SELECT {comment stuff})
ORDER BY created DESC LIMIT 7 OFFSET 7
我可以像这样手动构造正确的查询字符串:
$unionQuery = $articlesQuery->unionAll($commentsQuery);
$sql = $unionQuery->sql();
$sql = "($sql) ORDER BY created DESC LIMIT 7 OFFSET 7";
但我的自定义finder方法需要返回一个\Cake\Database\Query
对象,而不是一个字符串。
所以,
limit()
等方法应用于整个联合查询?注意:有a closed issue描述与此类似的东西(除了使用paginate($unionQuery)
),但没有提出如何克服这个问题的建议。
scrowler友好地建议了这个选项,但我认为它不会起作用。如果limit
设置为5,则完整结果集将为:
Article 9 --|
Article 8 |
Article 7 -- Page One
Article 6 |
Article 5 --|
Article 4 --|
Comment 123 |
Article 3 -- Here be dragons
Comment 122 |
Comment 121 --|
...
然后第1页的查询将起作用,因为(前五篇文章)+(前五项注释),按日期手动排序,并修剪为组合结果的前五项将产生第1-5条。
但第2页不起作用,因为5的offset
将适用于文章和评论,这意味着前5条评论(未包含在第1页中)将永远不会出现在结果中。
能够直接在unionAll()
返回的查询上应用这些子句是不可能的AFAIK,它需要更改API,使编译器知道在哪里放SQL,通过选项,新类型的查询对象,等等。
幸运的是,可以使用Query::epilog()
将SQL附加到查询中,因为它是原始的SQL片段
$unionQuery->epilog('ORDER BY created DESC LIMIT 7 OFFSET 7');
或查询表达式
$unionQuery->epilog(
$connection->newQuery()->order(['created' => 'DESC'])->limit(7)->offset(7)
);
这应该为您提供所需的查询。
应该注意的是,根据文档,Query::epilog()
要求字符串或\Cake\Database\ExpressionInterface
实例形式的具体\Cake\Database\Expression\QueryExpression
实现,而不仅仅是任何ExpressionInterface
实现,所以理论上后一个例子是无效的,即使查询编译器适用于任何ExpressionInterface
实现。
也可以将union查询用作子查询,这样可以在使用分页组件的环境中更容易,因为除了构建和注入子查询之外,您不必处理任何其他事情,因为paginator组件会能够简单地在主查询上应用顺序/限制/偏移。
/* @var $connection \Cake\Database\Connection */
$connection = $articles->connection();
$articlesQuery = $connection
->newQuery()
->select(['*'])
->from('articles');
$commentsQuery = $connection
->newQuery()
->select(['*'])
->from('comments');
$unionQuery = $articlesQuery->unionAll($commentsQuery);
$paginatableQuery = $articles
->find()
->from([$articles->alias() => $unionQuery]);
这当然也可以移到查找器中。