Laravel Query Builder 复杂查询案例:子查询实现分区查询 partition by
案例
案例:[Laravel 在文章列表中附帶上前10條評(píng)論?][1],在獲取文章列表時(shí)同時(shí)把每個(gè)文章的前10條評(píng)論一同查詢出來(lái)。
這是典型分區(qū)查詢案例,需要根據(jù) comments 表中的 post_id 字段進(jìn)行分區(qū),同時(shí)根據(jù)條件進(jìn)行排序,把符合條件的前 N 條是數(shù)據(jù)取出來(lái)。
在其他數(shù)據(jù)庫(kù)(Oracle, SQL Server,Vertica) 包含了 row_number partition by 這樣的函數(shù),能夠比較容易的實(shí)現(xiàn)。
比如在 SQL Server 中:
SELECT * FROM ( SELECT *, row_number() OVER (partition by post_id ORDER BY created_at desc) rank FROM comments where post_id in (1,2,3,4,5) ) b where rand < 11; 復(fù)制代碼在 mysql 中要復(fù)雜一些,我們先來(lái)看看上面案例中實(shí)現(xiàn)需求的幾種解決辦法。
解決辦法
方法1:
在 blade 中要顯示評(píng)論數(shù)據(jù)的地方 post->comments()->limit(10)
問(wèn)題:如果取了 20 條 Post 數(shù)據(jù),就會(huì)有 20 條取 comments 的 sql 語(yǔ)句,會(huì)造成執(zhí)行的 sql 語(yǔ)句過(guò)多。
不是非常可取,主要問(wèn)題會(huì)造成 SQL 語(yǔ)句過(guò)多,對(duì)數(shù)據(jù)庫(kù)服務(wù)器產(chǎn)生壓力,不過(guò)這里可以使用緩存來(lái)改進(jìn),但是不在本文章討論范圍里。
方法2:
直接通過(guò) with 把 Post 的所有 comments 數(shù)據(jù)都取出來(lái),在 blade 中 post->comments->take(10)
問(wèn)題:Laravel 會(huì)預(yù)先把文章所有的評(píng)論數(shù)據(jù)查詢出來(lái),如果文章的評(píng)論數(shù)據(jù)非常多,可能會(huì)造成內(nèi)存泄漏。
方法3:
$posts = Post::paginate(15);$postIds = $posts->pluck('id')->all();//找出符合條件的 comments ,同時(shí)定義 @post, @rank 變量,這里沒(méi)有用 all,get 等函數(shù),此時(shí)并不會(huì)執(zhí)行 SQL 語(yǔ)句。 $sub = Comment::whereIn('post_id',$postIds)->select(DB::raw('*,@post := NULL ,@rank := 0'))->orderBy('post_id');//把上面構(gòu)造的 sql 查詢作為子表進(jìn)行查詢,根據(jù) post_id 進(jìn)行分區(qū)的同時(shí) @rank 變量不斷+1 $sub2 = DB::table( DB::raw("({$sub->toSql()}) as b") )->mergeBindings($sub->getQuery())->select(DB::raw('b.*,IF (@post = b.post_id ,@rank :=@rank + 1 ,@rank := 1) AS rank,@post := b.post_id'));//取出符合條件的前10條comment $commentIds = DB::table( DB::raw("({$sub2->toSql()}) as c") )->mergeBindings($sub2)->where('rank','<',11)->select('c.id')->pluck('id')->toArray();$comments = Comment::whereIn('id',$commentIds)->get();$posts = $posts->each(function ($item, $key) use ($comments) {$item->comments = $comments->where('post_id',$item->id); }); 復(fù)制代碼會(huì)產(chǎn)生三條sql
select * from `posts` limit 15 offset 0;select `c`.`id` from (select b.*,IF ( @post = b.post_id ,@rank :=@rank + 1 ,@rank := 1 ) AS rank, @post := b.post_id from (select *,@post := NULL ,@rank := 0 from `comments` where `post_id` in ('2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16') order by `post_id` asc) as b) as c where `rank` < '11';select * from `comments` where `id` in ('180', '589', '590', '3736'); 復(fù)制代碼知識(shí)點(diǎn)
為什么不直接用原生 SQL 語(yǔ)句來(lái)實(shí)現(xiàn)?
這里之所以堅(jiān)持使用 Laravel Query Builder 來(lái)實(shí)現(xiàn),可以有效防止 SQL 注入,并且和 ORM 的 Model 對(duì)象關(guān)聯(lián)起來(lái)。
如果還有更多類似這種復(fù)雜的需求,歡迎聯(lián)系我 : )
討論交流
總結(jié)
以上是生活随笔為你收集整理的Laravel Query Builder 复杂查询案例:子查询实现分区查询 partition by的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: HP LoadRunner 12.02
- 下一篇: HeartBeat Install