【问题标题】:How do you modify a UNION query in CakePHP 3?如何在 CakePHP 3 中修改 UNION 查询?
【发布时间】:2015-06-05 10:31:01
【问题描述】:

我想在 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";

但我的自定义查找器方法需要返回 \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 页的查询将起作用,因为(前五篇文章)+(前五篇 cmets),按日期手动排序,并修剪到仅组合结果的前五篇将导致文章 1-5 .

但第 2 页不起作用,因为 5 的 offset 将应用于文章和 cmets,即前 5 个 cmets(未包含在第 1 页中),永远不会出现在结果中。

【问题讨论】:

  • 一种选择是将限制和偏移分别应用于文章和 cmets
  • 我不能 100% 确定,但我认为这实际上不会产生预期的分页结果。就像一个完整的结果集,按created 字段排序,在任何新的 cmets 之前返回 20 篇新文章,那么第 1 页是正确的,但第 2 页会——该死的,我只需要在答案中输入这个。 ..
  • 我明白了-您需要在整个联合中应用顺序和限制(如您的示例中所示)-不确定如何在 Cake 中实现这一点。祝你好运
  • 如果你执行 $unionQuery->select()->limit(7)->offset(7) 会发生什么?我不知道 CakePHP ORM 但阅读文档我认为它可能会与您的联合创建一个子查询...
  • 在 Cake.Database.Query 的文档中(这是 unionAll 返回的)有这样的东西: $query->select(['sub' => $countQuery]); // FROM (SELECT ...) sub

标签: cakephp cakephp-3.0


【解决方案1】:

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

Query::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\Expression\QueryExpression 实例形式的字符串或具体的 \Cake\Database\ExpressionInterface 实现,而不仅仅是 任何 ExpressionInterface 实现,因此理论上后一个示例是无效的,即使查询编译器适用于任何 ExpressionInterface 实现。

使用子查询

也可以将联合查询用作子查询,这将使使用分页组件的上下文中的事情变得更容易,因为除了构建和注入子查询之外,您无需处理任何事情,因为分页器组件将能够简单地在主查询上应用订单/限制/偏移量。

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

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

【讨论】:

  • 太棒了! Query::epilog() 不在文档中,我在阅读类文件时忽略了它。谢谢!
  • 警告:如果通过epilog() 应用限制,分页组件似乎无法找到记录总数。或者更确切地说,它假定记录总数等于限制。这是由于count() 的工作方式:book.cakephp.org/3.0/en/orm/…
  • @PhantomWatson 您应该可以通过使用自定义计数器(请参阅Query::counter())来解决此问题,现在无法对其进行测试,但理论上获取查询的副本并取消设置 Epilog 应该这样做,从那里开始在查询副本上调用Query::count() 应该是安全的。稍后我将对其进行测试并更新我的答案。
  • 我太快绝望了。在自定义查找方法的末尾使用$unionQuery->counter(...),即使在使用epilog() 之后,也能完美运行。谢谢!
  • @ndm 这在上一个版本中有所改变吗? $unionQuery->epilog('LIMIT 20 OFFSET 0'); 当我使用分页时,将限制和偏移量放在联合中的两个查询中。
猜你喜欢
  • 1970-01-01
  • 2016-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多