jonasraoni/query-builder-extensions

Laravel Illuminate\Database\Query\Builder 扩展。

1.0.0 2023-06-28 08:23 UTC

This package is auto-updated.

Last update: 2024-09-28 11:00:56 UTC


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;
}