计算Eloquent中两个记录之间的值差异

时间:2017-05-01 18:06:42

标签: php mysql laravel eloquent laravel-5.4

我想用雄辩的方式计算两个记录之间的差异。例如,我有以下表格:

----------------------------------
| 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。

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

有什么想法吗?

1 个答案:

答案 0 :(得分:2)

  

我不介意雄辩的小型性能影响,而是循环播放   结果集计算差异..我的意思是,如果我有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

因此,“雄辩的小绩效影响”是:

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

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

现在你仍然可以尝试在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();

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

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

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

$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和assotiative数组的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

您首选的解决方案是最慢和最不可靠的解决方案。所以你的问题的答案是你的问题的一个糟糕的解决方案。