如何修改 CakePHP 3 中的 UNION 查询?
How do you modify a UNION query in CakePHP 3?
我想在 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";
但我的自定义查找器方法需要 return 一个 \Cake\Database\Query
对象,而不是字符串。
所以,
- 有没有办法将
limit()
之类的方法应用于整个联合查询?
- 如果没有,有没有办法将 SQL 查询字符串转换为查询对象?
注意:
有 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 条评论(不是' t 包含在第 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
实现,而不仅仅是 any ExpressionInterface
实现,所以理论上后一个示例是无效的,即使查询编译器适用于任何 ExpressionInterface
实现。
使用子查询
也可以将联合查询用作子查询,这将使使用分页组件的上下文中的事情变得更容易,因为除了构建和注入子查询之外,您无需处理任何其他事情,因为分页器组件可以简单地在主查询上应用 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]);
这当然也可以移到取景器中。
我想在 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";
但我的自定义查找器方法需要 return 一个 \Cake\Database\Query
对象,而不是字符串。
所以,
- 有没有办法将
limit()
之类的方法应用于整个联合查询? - 如果没有,有没有办法将 SQL 查询字符串转换为查询对象?
注意:
有 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 条评论(不是' t 包含在第 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
实现,而不仅仅是 any ExpressionInterface
实现,所以理论上后一个示例是无效的,即使查询编译器适用于任何 ExpressionInterface
实现。
使用子查询
也可以将联合查询用作子查询,这将使使用分页组件的上下文中的事情变得更容易,因为除了构建和注入子查询之外,您无需处理任何其他事情,因为分页器组件可以简单地在主查询上应用 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]);
这当然也可以移到取景器中。