[英]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.