【问题标题】:Calculating value differences between two records in Eloquent在 Eloquent 中计算两条记录之间的值差异
【发布时间】:2017-09-29 03:49:31
【问题描述】:

我想用 eloquent 计算两条记录之间的差异。例如,我有下表:

----------------------------------
| Id | value | type              |
----------------------------------
| 1  | 100   | FOO               |
| 2  | 500   | FOO               |
| 3  | 800   | FOO               |
| 4  | 200   | BAR               |
| 5  | 600   | BAR               |
| 6  | 1000  | FOO               |
----------------------------------

假设模型名称是 FooBar,所以当我过滤表格时,例如使用FooBar::where('type', 'FOO')->get();,我会得到以下结果:

----------------------------------
| Id | value | type  | diff      |
----------------------------------
| 1  | 100   | FOO   | 0         |
| 2  | 500   | FOO   | 400       | (500 - 100)
| 3  | 800   | FOO   | 300       | (800 - 500)
| 6  | 1000  | FOO   | 200       | (1000 - 800)
----------------------------------

现在,也许我可以通过原始查询更轻松地实现这一点,比如声明变量来存储以前的记录(例如:SET @id := 0 并在 SELECT 语句中设置它)。但在这种情况下,如果可能,我更喜欢使用 Eloquent。

我目前的解决方法是循环结果集并手动计算,我担心它会影响性能。

有什么想法吗?

【问题讨论】:

  • 不要无缘无故地把事情复杂化。如果你想从性能中获得最后一点,你甚至不应该使用 Eloquent。
  • 我不介意 eloquent 对性能的小影响,但循环遍历结果集以计算差异。我的意思是,如果我有数千条记录,则 cmon 一个一个循环是一个粗略的想法

标签: php mysql laravel eloquent laravel-5.4


【解决方案1】:

我不介意 eloquent 对性能的小幅影响,但循环往复 计算差异的结果集..我的意思是,如果我有的话,cmon 数千条记录,一条一条循环是粗略的想法

那我给你一个惊喜——这是一个小的性能测试:

class Seq extends Eloquent {
    protected $table = 'helper.seq';
    protected $primaryKey = 'i';
}

Route::get('/loop', function () {
    $limit = 10000;

    $st = microtime(true);
    $data = Seq::orderBy('i')->take($limit)->get();
    var_dump(microtime(true) - $st);

    $st = microtime(true);
    foreach ($data as $row) {
        $row->i;
    }
    var_dump(microtime(true) - $st);

    $pdo = DB::getPdo();
    $st = microtime(true);
    $data2 = $pdo
        ->query("select * from helper.seq order by i limit $limit")
        ->fetchAll(PDO::FETCH_OBJ);
    var_dump(microtime(true) - $st);

    $st = microtime(true);
    foreach ($data2 as $k => $row) {
        if ($k == 0) {
            $row->diff = 0;
        } else {
            $row->diff = $row->i - $data2[$k-1]->i;
        }
    }
    var_dump(microtime(true) - $st);
});

helper.seq 是一个只有一个 int 列和 1M 行的表。

结果是:

0.779045s <- Fetch from DB with Eloquent

1.022058s <- Read Eloquent data (Only one column and do nothing with it)

0.020002s <- Fetch from DB with PDO

0.009999s <- Calculate all diffs in a loop

所以“eloquent 对性能的影响很小”是:

  • 从数据库中获取数据时,比使用普通 PDO 和 stdClass 慢将近 20 倍。
  • 在循环中读取属性/属性时,至少比 stdClass 慢 100 倍。

因此,如果您想提高性能,请在处理大量数据时切换到普通 PDO,或者至少使用默认的 Builder。

现在你仍然可以尝试在 MySQL 中完成这项工作,但使用 Eloquent 的要求没有意义。

但是您可以尝试混合版本 - 使用 Eloquent 构建查询,但将其转换为 Database\Query\Builder 和 getQuery()。

$fooBars = FooBar::where('type', 'FOO')->orderBy('id')
    ->getQuery()
    ->select(['*', DB::raw('coalesce(`value` - @last, 0)'), DB::raw('@last := `value`')])
    ->get();

但我总是会避免在应用程序代码中以这种方式使用会话变量,因为我已经看到许多这样的解决方案在版本升级后返回错误/意外的结果。

仍然不相信?以下是其他一些测试:

在 Eloquent 查询中使用会话变量转换为Database\Query\Builder:

$st = microtime(true);
$data = Seq::getQuery()
    ->select(['*', DB::raw('coalesce(i - @last, 0)'), DB::raw('@last := i')])
    ->orderBy('i')->take($limit)->get();
var_dump(microtime(true) - $st);

// runtime: 0.045002s

使用转换后的 Eloquent 查询的 PHP 解决方案:

$st = microtime(true);
$data2 = Seq::getQuery()->orderBy('i')->take($limit)->get();
foreach ($data2 as $k => $row) {
    if ($k == 0) {
        $row->diff = 0;
    } else {
        $row->diff = $row->i - $data2[$k-1]->i;
    }
}
var_dump(microtime(true) - $st);

// runtime: 0.039002

带有普通 PDO 和 stdClass 的 PHP 解决方案

$st = microtime(true);
$data3 = $pdo
    ->query("select * from helper.seq s1 order by i limit $limit")
    ->fetchAll(PDO::FETCH_OBJ);
foreach ($data3 as $k => $row) {
    if ($k == 0) {
        $row->diff = 0;
    } else {
        $row->diff = $row->i - $data3[$k-1]->i;
    }
}
var_dump(microtime(true) - $st);

// runtime: 0.035001s

带有普通 PDO 和关联数组的 PHP 解决方案:

$st = microtime(true);
$data4 = $pdo
    ->query("select * from helper.seq s1 order by i limit $limit")
    ->fetchAll(PDO::FETCH_ASSOC);
foreach ($data4 as $k => $row) {
    if ($k == 0) {
        $row['diff'] = 0;
    } else {
        $row['diff'] = $row['i'] - $data4[$k-1]['i'];
    }
}
var_dump(microtime(true) - $st);

// runtime: 0.027001s

您首选的解决方案是最慢且最不可靠的。所以你的问题的答案对你的问题来说是一个不好的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-18
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 2012-12-16
    • 2017-12-24
    相关资源
    最近更新 更多