jonasraoni / query-builder-extensions
Laravel Illuminate\Database\Query\Builder 扩展。
Requires
- php: >=8.2.0
README
目前只提供扩展,使其更容易处理分页结果集。两种方法都不会缓冲结果集,因此一旦页面被消耗,就会发出新的查询以检索下一页。
该包还有一个更安全的方法来计算记录数(getCount()),它应该比Laravel的count()方法运行得更快(通过删除ORDER BY子句)并且更可靠(与GROUP BY子句一起工作)。
使用 getCount() 安全地计算记录数
getCount(): int
如果你有一个 SELECT field FROM test GROUP BY field,Laravel的count()会将其转换为SELECT COUNT(0) FROM test GROUP BY field,可能会检索N条记录。Laravel将仅检索第一条记录的值,这可能会打破用户的预期。
此getCount()方法将生成一个SELECT COUNT(0) FROM (SELECT 0 FROM test GROUP BY field),这将检索正确的记录数。
懒加载分页器
paginateLazily(Builder $query, int $rows): \Generator
检索一个生成器,它将遍历每一页的所有记录(由$rows分割)。
此方法不会触及你的查询,它只是一个辅助工具,因此你必须自己添加排序。
动态分页器
bufferedIterator(Builder $query, array $sortMap, int $rows): \Generator
它与前一种方法做同样的事情... 但是
- 它可能性能更好,因为它不使用
LIMIT子句,随着页面的推进,这个子句会变得较慢。 - 确保不会重新访问过去的记录,并且由于对之前的页面进行更新(删除/插入/更新的记录导致位移效果),记录不会丢失/跳过。
因此我认为它非常适合处理大量结果集,因为在需要时可以消费数据,而不会因为更新而出现太多问题。
详情
为了避免跳过/重新处理过去的记录,代码会跟踪页面中最后处理的记录。
数组 $sortMap
此参数用于告诉分页器哪些字段应该用于排序,并帮助它检索最后一个记录的键值(因此它知道下一页应该跳过什么)。
- “键”表示将在
ORDER BY子句中使用的值(因此它可以是一个有效的ORDER BY字段,如table.field DESC) - “值”必须映射到一个字段名,该字段名在
SELECT子句中可用,并持有与ORDER BY表达式相同的值。也支持可调用对象(接收一个对象,页面的最后一个记录,并必须返回预期的数据)。
use JonasRaoni\QueryBuilder\Extensions; Extensions::extend(); $records = $connection->table('posts') ->select('id', 'date', 'title') // Will produce an "ORDER BY id DESC, IF(date IS NULL, 0, 1)" ->bufferedIterator( [ // Maps the given sort expression to the "id" field (must be available in the "SELECT") 'id DESC' => 'id', // Maps the given sort expression using a callable 'IF(date IS NULL, 0, 1)' => function ($record) { return $record->date ? 1 : 0; } ] ); foreach ($records as $record) { echo $record->id; }
要求
- 查询必须只按给定的字段/键进行排序(该方法会自动添加排序,因此不需要手动进行)。
- 不允许有重复的行! 这可能会导致意外的行为。来自
$sortMap的值必须能够完美地识别一行(因此请注意大小写不敏感的比较,例如'a' > 'A')。
PS:如果还不清楚,这里有一个当你使用标准分页方法时可能会遇到的问题的例子
- 假设你有一个基于查询的分页结果集:
SELECT * FROM posts WHERE active = 1 ORDER BY id - 假设你在第10页,这时有人更新了你之前页面访问的所有记录(例如:
UPDATE posts SET active = 0 WHERE id < :lastVisitedId),那么当你翻到第11页时,你会有一个小小的惊喜!一些记录会被跳过... 反过来也可能发生,比如有人对前几页进行操作添加了新记录(例如:UPDATE posts SET active = 1 WHERE active = 0 AND id < :lastVisitedId)。
一般用法
安装包
composer require jonasraoni/query-builder-extensions
该包只有一个类(JonasRaoni\QueryBuilder\Extensions),可以用两种方式使用
A. 宏扩展
调用extend方法,以扩展所有Builder实例
use JonasRaoni\QueryBuilder\Extensions; Extensions::extend(); $records = $connection->table('test') ->select('field') ->orderBy('field') ->paginateLazily(100); foreach ($records as $record) { echo $record->id; } echo $connection->table('test')->getCount();
B. 直接调用方法
use JonasRaoni\QueryBuilder\Extensions; $queryBuilder = $connection ->table('test') ->select('field') ->orderBy('field'); $records = Extensions::paginateLazily($queryBuilder, 100); foreach ($records as $record) { echo $record->id; }