当前位置: 首页 > 后端技术 > PHP

PHP&MySQL“一对一数据关联”最佳实践

时间:2023-03-30 00:45:25 PHP

前言在开发过程中,我们通常会遇到很多“一对一”的数据处理。很多时候,我们想要获取的是一个列表,然后列表中的单条记录对应到另一个表来实现业务。比如下面的商品信息和商品详情这两个表,只是用了基本的字段来做演示,实际开发起来可能会复杂很多。下面演示代码中的数据库连接是通过PDO处理的。表结构goodscolumntypecommentidint(11)自增主键IDtitlevarchar(100)商品名称pricedecimal(10,2)商品价格covervarchar(100)商品封面goods_detail列类型commentidint(11)自增主键IDgoods_idint(11)商品IDcontentvarchar(5000)商品图文介绍Elementary坦白说,无论是在公司还是在一些开源项目中,我都看到过如下代码。$query=$db->query('select*fromgoods');$result=$query->fetchAll();//选项1foreach($resultas$key=>$item){$query=$db->query('select*fromgoods_detailwheregoods_id='.$item['ID']);$result[$key]['goods_detail']=$query->fetch();}var_dump($result);//方案二foreach($resultas&$item){$query=$db->query('select*fromgoods_detailwheregoods_id='.$item['id']);$item['goods_detail']=$query->fetch();}unset($item);var_dump($result);//方案三$result=array_map(function($item){$query=$db->query('select*fromgoods_detailwheregoods_id='.$item['id']);$item['goods_detail']=$query->fetch();return$item;},$result);变量转储($结果);这是最暴力的方法,也是立竿见影的方法,而且代码在第一种方案中似乎很繁琐,是不是?如果你学过引用这一段,应该知道第二种用法,直接使用引用来操作源数据。当然最好不要忘记最后unset$item。除了第二种方法,我们还可以使用第三种方法,使用array_map,确实这和第二种方法没什么区别,但是有一个非常大的问题:N+1的数据库查询。从执行上我们可以看出,除了query列表中的一条sql外,每一条记录对应的query都需要执行一条sql,从而产生了额外的query。想想如果查询没有限制。情况会怎样?Advanced看到这里,可能有人想到了另一种方案,先查询list,然后取出list中的goods_id用in查询,然后循环赋值给list,看代码。$goods_id=array_column($result,'id');$goods_id_str=implode(',',$goods_id);$query=$db->query(sprintf('select*fromgoods_detailwheregoods_idin(%s)',$goods_id_str));$goods_detail_list=$query->fetchAll();foreach($resultas&$item){$item['goods_detail']=array_first($goods_detail_list,function($item1){返回$item['id']==$item1['goods_id'];});}unset($item);var_dump($result);/***来自Laravel*/if(!function_exists('value')){functionvalue($value){返回$valueinstanceof闭包?$值():$值;}}/***来自Laravel*/if(!function_exists('array_first')){/***@param$array*@paramcallable|null$callback*@paramnull$default*@returnmixed*/functionarray_first($array,callable$callback=null,$default=null){if(is_null($callback)){if(empty($array)){返回值($default);}foreach($arrayas$item){返回$item;}}foreach($arrayas$key=>$value){if(call_user_func($callback,$value,$key)){返回$value;}}返回值($默认值);}}这段代码中,我们完美的避免了N+1的困境,使用in查询,然后遍历数组,然后使用array_first方法查找并传递给goods_detail索引,虽然这样效率相对来说要高很多比第一个,但它并不完美。接下来我们看最后一个关于array_first的解决方案。你可以阅读我的另一篇文章“PHP多维数组中的array_find”。最佳实践$goods_detail_list_by_keys[$item['id']]:null;//php7.1+//$item['goods_detail']=$goods_detail_list_by_keys[$item['id']]??空;}未设置($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'=>'defaultcontent','id'=>null,'goods_id'=>空,];foreach($resultas&$item){$tmp=array_key_exists($item['id'],$goods_detail_list_by_keys)?$goods_detail_list_by_keys[$item['id']]:[];//php7.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指出文章内容中的错误。