如何修改CakePHP 3中的UNION查询?

问题描述 投票:5回答:1

我想在CakePHP 3.0.0中对联合查询进行分页。通过使用custom finder,我几乎完美地工作,但我找不到任何方法让limitoffset适用于联合,而不是任何一个子查询。

换句话说,这段代码:

$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()等方法应用于整个联合查询?
  • 如果没有,有没有办法将SQL查询字符串转换为Query对象?

注意:有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页中)将永远不会出现在结果中。

cakephp cakephp-3.0
1个回答
5
投票

能够直接在unionAll()返回的查询上应用这些子句是不可能的AFAIK,它需要更改API,使编译器知道在哪里放SQL,通过选项,新类型的查询对象,等等。

查询:: epilog()来救援

幸运的是,可以使用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]);

这当然也可以移到查找器中。

© www.soinside.com 2019 - 2024. All rights reserved.