簡體   English   中英

如何從 Laravel 中的自定義查詢中分塊結果

[英]How to chunk results from a custom query in Laravel

我有一個自定義查詢,它從舊系統中獲取數據並將其映射到新系統中的模型。 查詢如下所示:

$companies = DB::connection('legacy')->select("...");

由於數據量很大,我想使用 Eloquent 的塊功能(只是從他們的文檔中復制的示例代碼):

User::chunk(200, function($users)
{
    foreach ($users as $user)
    {
        //
    }
});

我該如何實施?


編輯:我的代碼現在看起來像這樣,導致沒有響應:

DB::connection('legacy')->select("SELECT * FROM companies")->chunk(200, function($companies) {
    foreach ($companies as $company) {
        // dd($company);
        $entity       = Entity::firstOrNew(['external_id' => $company->companyKey]);
        $entity->name = $company->companyName;
        $entity->save();
    }
});

chunk功能僅適用於 Eloquent 模型和 QueryBuilder 請求,例如

DB::table('tbl')->where('num', '>', 3)->chunk(500, function($rows) {
    // process $rows
});

但它不適用於DB::select('...')請求。 您需要使用 QueryBuilder 請求,或使用底層 PDO 對象來查詢數據庫,例如:

$pdo = DB::getPdo();
$sth = $pdo->prepare("SELECT ....");
$sth->execute();
while ($row = $sth->fetch(PDO::FETCH_ASSOC))
{
    // ...
}

嘗試這樣的事情:

<?php

$max = 100;
$total = DB::connection('legacy')->select("...")->count();
$pages = ceil($total / $max);
for ($i = 1; $i < ($pages + 1); $i++) {
    $offset = (($i - 1)  * $max);
    $start = ($offset == 0 ? 0 : ($offset + 1));
    $legacy = DB::connection('legacy')->select("...")->skip($start)->take($max)->get();
    /* Do stuff. */
}

基本上復制了 Laravel 的分頁器所做的事情,而沒有額外的開銷。

更新: 2018 年 3 月,查詢構建器類中添加了一個新函數。 現在可以使用fromSub實現相同的fromSub

$subQuery = DB::table('users')->where(...);

DB::query()->fromSub($subQuery, 'alias')->orderBy('alias.id')->chunk(200, function ($chunk) {
    // Do something
});

並使用不同的連接開始DB::connection('legacy')->query()


舊答案:偶然發現這個問題,但在某些情況下可能會派上用場的小技巧。

$query = 'SELECT * FROM ... JOIN ... UNION ... WHATEVER ... GROUP BY';

// This is the important part:
$query = '(' . $query . ') somealias';

DB::connection('legacy')->table(DB::raw($query))->chunk(1000, function($rows){
    // Do something
});

查詢 laravel 執行然后是這樣的:

select * from (...) somealias LIMIT ... OFFSET ...

這應該至少在 Laravel 5.1 中有效。 但我不明白為什么它不應該在 4+ 中工作。

deyes 的回答有一個錯誤。

原樣

如果 'legacy' 表有 'id' 列並且有 1,2,3,4,5 ... 100 個編號的數據。

<?php

$max = 10;
$total = DB::connection('legacy')->select("...")->count();
$pages = ceil($total / $max);
for ($i = 1; $i < ($pages + 1); $i++) {
    $offset = (($i - 1)  * $max);
    $start = ($offset == 0 ? 0 : ($offset + 1));
    $legacy = DB::connection('legacy')->select("...")->skip($start)->take($max)->get();
    /* Do stuff. */

    $legacyIds = $legacy->lists("id");
    echo "i = " . $i . ": \n";
    print_r($legacyIds);
}

//Result
i = 1: 
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
    [9] => 10
)
i = 2: 
Array
(
    [0] => 12
    [1] => 13
    [2] => 14
    [3] => 15
    [4] => 16
    [5] => 17
    [6] => 18
    [7] => 19
    [8] => 20
    [9] => 21
) ...

去做

$max = 10;
$total = DB::connection('legacy')->select("...")->count();
$pages = ceil($total / $max);
for ($i = 1; $i < ($pages + 1); $i++) {
    $offset = (($i - 1)  * $max);
    $legacy = DB::connection('legacy')->select("...")->skip($offset)->take($max)->get();
    /* Do stuff. */
}

此頁面上的許多答案都會查找所有記錄,以計算有多少“頁面”。 這可能很慢並且不需要,因為我們沒有分頁我們正在分塊。 我們只需要在分頁顯示用戶時知道總頁數。

因此,在開始時獲取所有記錄計數的替代方法是執行以下操作:

    $recordsRemaining = true;
    $lookupsCompleted = 0;
    $chunkSize = 200;

    while($recordsRemaining){
       $companies = DB::connection('legacy')->select("...")->skip($chunkSize*$lookupsCompleted)->take($chunkSize)->get();

       if($legacy->count() < $chunkSize){
          $recordsRemaining = false;
       }
       foreach($companies as $company){
          //Do something
       }

       $lookupsCompleted++;
    }

這與接受的答案完全相同,但效率更高。

這些答案都不適合我。 我根據@deyes 的回答創建了自己的函數。

private static function chunk($query, $max, $function) {
    $counter = preg_replace('/SELECT (.*?) FROM/', 'SELECT COUNT(*) FROM', $query);
    $total = DB::connection('legacy')->select($counter)[0];
    $total = (array)$total;
    $total = $total['COUNT(*)'];

    $pages = ceil($total / $max);

    for ($i = 1; $i < ($pages + 1); $i++) {
        $offset = (($i - 1)  * $max);
        $start = ($offset == 0 ? 0 : ($offset + 1));
        $items = DB::connection('legacy')->select($query . ' LIMIT ' . $offset . ', ' . $max);

        $function($items);

        unset($items);
    }
}

用法

YourClass::chunk('SELECT * FROM tablename', 50, function($items) {
    //Work with $items.
});

請注意,這是一個簡單的快速修復,您的查詢可能必須相當簡單,因為我正在使用搜索替換來構建計數查詢,而我只是將 LIMIT X, Y 附加到查詢的末尾,但它有效為了我。

我相信您可以在查詢構建器上使用chunk 例如

DB::connection('legacy')->select("...")->chunk(200, function($companies){
    //do something with $companies
});

嘗試使用orderBy子句:

DB::table('tbl')->where('num', '>', 3)->orderBy('id')->chunk(500, function($rows) {
    // process $rows
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM