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

时间:2015-03-31 22:19:28

标签: cakephp cakephp-3.0

我想在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页无法正常使用,因为offset 5将适用于文章和评论,这意味着前5条评论(未包含在第1页中) ),永远不会出现在结果中。

1 个答案:

答案 0 :(得分: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实现,而不仅仅是 any ExpressionInterface实现,理论上后一个例子是无效的,即使查询编译器适用于任何ExpressionInterface实现。

使用子查询

也可以将联合查询用作子查询,这样可以在使用分页组件的情况下使事情变得更容易,因为您不必处理除了构建和之外的任何事情。注入子查询,因为paginator组件只能在主查询上应用order / limit / offset。

/* @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]);

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