摘要:前言在開發過程中,通常會遇到很多一對一數據的處理情況。關于可以看我的另一篇文章多維數組中的。最佳實踐這一次,我們用到了其他兩個函數。勘誤感謝評論區對文章內容錯誤之處的指出。
前言
在開發過程中,通常會遇到很多 一對一 數據的處理情況。而很多時候我們會要取到的是一個列表,然后列表的單條記錄的對應另外一張表,來實現業務。比如下面的商品信息 和 商品詳情 兩個表,這里為了演示只是使用了基礎字段,實際開發中可能會復雜的多,下方演示代碼中數據庫連接使用 PDO 進行處理。
表結構 goods列 | 類型 | 注釋 |
---|---|---|
id | int(11) 自動增量 | 主鍵ID |
title | varchar(100) | 商品名稱 |
price | decimal(10,2) | 商品價格 |
cover | varchar(100) | 商品封面 |
列 | 類型 | 注釋 |
---|---|---|
id | int(11) 自動增量 | 主鍵ID |
goods_id | int(11) | 商品ID |
content | varchar(5000) | 商品圖文介紹 |
坦言,無論是在公司,還是在一些開源項目上,我都看到過如下的代碼。
$query = $db->query("select * from goods"); $result = $query->fetchAll(); // 方案一 foreach($result as $key => $item){ $query = $db->query("select * from goods_detail where goods_id=" . $item["id"]); $result[$key]["goods_detail"] = $query->fetch(); } var_dump($result); // 方案二 foreach($result as &$item){ $query = $db->query("select * from goods_detail where goods_id=" . $item["id"]); $item["goods_detail"] = $query->fetch(); } unset($item); var_dump($result); // 方案三 $result = array_map(function($item){ $query = $db->query("select * from goods_detail where goods_id=" . $item["id"]); $item["goods_detail"] = $query->fetch(); return $item; },$result); var_dump($result);
這是最暴力的方式,也是立桿見影,而且方案一看起來代碼貌似還很繁瑣,不是嗎?如果學過 引用這一節的朋友,應該知道第二種用法,直接用引用去操作源數據,當然最后最好別忘了 unset 掉 $item,除了第二種,我們還可以用第三種方式,使用 array_map,誠然,這和第二種方式沒什么區別,但是這其中有著一個非常大的問題:數據庫查詢的N+1 。從執行中我們就可以看到,除了查詢列表的一條 SQL 外,每查詢一條記錄對應的都需要執行一條 SQL ,導致了額外的查詢,想想一下如果查詢沒有 limit 限制。會是什么樣子的情況?
進階看到這里,有人可能會想到了另一種方案來,先查詢列表,然后取出列表里面的 goods_id 之后使用 in 查詢,然后再循環分配給列表,看代碼。
$goods_id = array_column($result,"id"); $goods_id_str = implode(",",$goods_id); $query = $db->query(sprintf("select * from goods_detail where goods_id in (%s)",$goods_id_str)); $goods_detail_list = $query->fetchAll(); foreach($result as &$item){ $item["goods_detail"] = array_first($goods_detail_list,function($item1){ return $item["id"] == $item1["goods_id"]; }); } unset($item); var_dump($result); /** * 來自 Laravel */ if (!function_exists("value")) { function value($value) { return $value instanceof Closure ? $value() : $value; } } /** * 來自 Laravel */ if (!function_exists("array_first")) { /** * @param $array * @param callable|null $callback * @param null $default * @return mixed */ function array_first($array, callable $callback = null, $default = null) { if (is_null($callback)) { if (empty($array)) { return value($default); } foreach ($array as $item) { return $item; } } foreach ($array as $key => $value) { if (call_user_func($callback, $value, $key)) { return $value; } } return value($default); } }
在這個代碼中,我們完美避開了 N+1 的窘境,使用了in查詢,然后遍歷數組,再使用 array_first 方法來查找后傳遞給 goods_detail 索引,雖然這樣的效率相比第一次的要高了很多,但是并不完美,接下來來看最后一種方案。關于 array_first 可以看我的另一篇文章 『PHP 多維數組中的 array_find』。
最佳實踐$goods_detail_list_by_keys = array_column($goods_detail_list,null,"goods_id"); foreach($result as &$item){ $item["goods_detail"] = array_key_exists($goods_detail_list_by_keys,$item["id"]) ? $goods_detail_list_by_keys[$item["id"]] : null ; // php 7.1+ // $item["goods_detail"] = $goods_detail_list_by_keys[$item["id"]] ?? null; } unset($item); var_dump($result);
這一次,我們用到了其他兩個函數。array_column 、 array_key_exists,接下里一一道來,其實在array_column的官方手冊中的我們就能 Example #2 中就介紹了我們想要的方法。套用在這里就是重置goods_detail_list 里面元素的 key 為 單個元素下的 goods_id。在后面我們直接用 array_key_exists 判斷是否存在,然后做出相應的處理就好了。在這里我們還可以做另外一個操作,那就是默認值,因為有時候,數據有可能會因對不上,如果查出來直接返回給前端,前端沒有預料到這種情況沒有做容錯處理就會導致前端頁面崩潰,下面來改寫一下代碼
// 在 「進階」 板塊中,我們用到了 「array_first」 函數,該函數第三個參數可以直接設置默認值,我們就不多講了,主要講講最后一個 $goods_detail_default = [ "content" => "默認內容", "id" => null, "goods_id"=> null, ]; foreach($result as &$item){ $tmp = array_key_exists($item["id"] , $goods_detail_list_by_keys) ? $goods_detail_list_by_keys[$item["id"]] : [] ; // php 7.1+ // $tmp = $goods_detail_list_by_keys[$item["id"]] ?? []; $item["goods_detail"] = array_merge($goods_detail_default,$tmp); } unset($item); var_dump($result);結束
看到這里就算是完結了但是有的朋友會說,為什么不用 leftJoin 來處理?確實,在處理一對一關系中很多時候我們都會選擇 innerJoin 或者 leftJoin 來進行處理,一條 SQL 就能搞定,很少會用到類似于這種方案,其實不然,在主流的框架中,默認的解決方案幾乎都是這樣處理的,比如Laravel、ThinkPHP,考慮到的場景會有很多,比如有的時候我只是需要按需取一部分的,或者我需要根據我后面的業務結果來決定是不是要加載一對一,然而這種情況下 join 似乎就不太適合。
勘誤感謝評論區 @maclxf 對文章內容錯誤之處的指出。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31121.html
摘要:邢舟開源與開放標準工程院軟件工程師背景回顧月日,中國社區全新改版線上課堂,邀請邢舟老師以直播的方式進行了一場以存儲概覽為題的線上講解,反響熱烈。為更好地為學員整合問答,中國社區特別整理了本期模塊,感謝邢舟老師百忙之中進行校對。 邢舟 /IBM 開源與開放標準工程院軟件工程師 背景回顧:8 月 2 日 20:00,K8sMeetup 中國社區全新改版線上課堂,邀請邢舟老師以直播的方式進行...
摘要:最近,我需要在開發的事件管理系統中實現搜索功能。今天,我會介紹整個過程以及如何構建靈活且可擴展的搜索系統。這將是個挑戰前端的條件過濾的截圖。像剛剛的情況下搜索用戶時加上一個過濾器再返回搜索結果。 showImg(https://segmentfault.com/img/remote/1460000018654283?w=1680&h=494); 最近,我需要在開發的事件管理系統中實現搜...
摘要:如果要消滅大程序,那就得切分,做好切分必然離不開高內聚低耦合的核心思想。分布式系統關注點高內聚低耦合詳解這篇聊的就是這個。也是分布式系統的分治思想體現。垂直切分垂直切分有時候也會被稱作縱向切分。題外話不到迫不得己,盡量避免進行水平切分。 如果第二次看到我的文章,歡迎下方掃碼訂閱我的個人公眾號(跨界架構師)喲~本文長度為5389字,建議閱讀14分鐘。堅持原創,每一篇都是用心之作~ 沒...
閱讀 830·2021-09-22 15:18
閱讀 1181·2021-09-09 09:33
閱讀 2758·2019-08-30 10:56
閱讀 1184·2019-08-29 16:30
閱讀 1488·2019-08-29 13:02
閱讀 1458·2019-08-26 13:55
閱讀 1645·2019-08-26 13:41
閱讀 1941·2019-08-26 11:56