Code前端首页关于Code前端联系我们

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

terry 2年前 (2023-09-25) 阅读数 62 #后端开发

在开发过程中,通常会遇到很多一对一的数据处理情况。很多时候我们想要的是一个列表,然后列表中的一条记录对应另一个表来实现业务。例如,下面两个表是产品信息产品详细信息。这里仅使用基本字段进行演示。在当前的开发中,它可能要复杂得多。 下面演示代码中的数据库连接是通过PDO处理的。

表结构

商品

类型评论
id主键id
标题varchar(100) 产品名称
价格十进制(10,2)产品价格
封面goods_de Schwäif
类型 评论
idint(11).文字介绍

初级

说实话,我见过下面的代码,无论是在公司还是在开源项目上。

$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);

这是最暴力的方式,也是立杆的影子,而且选项1看起来代码还是很繁琐不是吗?学过引用这一节的朋友应该使用第二种方式,就是直接使用引用来操作源数据。当然,最好不要忘记最后删除$item。除了第二种方式之外,我们还可以使用第三种方式。 ,用array_map,诚然,这和第二种方法没有什么不同,但是有一个非常大的问题:数据库查询的N+1。从执行中我们可以看到,除了查询列表中的SQL之外,每查询一条记录都需要执行一条SQL,从而产生了额外的查询。请记住请求是否没有 limit 限制。那会是什么样子?

进阶

看到这里,可能有人会想到另一个解决方案,先查询列表,然后取出列表中的goods_id然后❿❿❿,然后循环赋值给列表,参见代码。

$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查询中使用,然后传入数组,搜索完之后再使用❝数组❝方法,就可以了将其传递给 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的官方手册中,我们可以在示例#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($goods_detail_list_by_keys,$item['id']) ? $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);

End

看看这就完成了,但是有的朋友说为什么不使用leftJoin

来处理吗?事实上,在处理一对一关系很多时候我们会选择innerJoin 。它可以通过单个 SQL 完成,并且很少使用。与这种方案类似,其实在主流框架中,标准方案几乎都是这样处理的,比如Laravel、ThinkPHP。有很多场景需要考虑。例如,有时我只需要根据要求参加。 ,或者我必须根据我后续的业务结果来决定是否进行一对一加载。然而,在这种情况下,join似乎不合适。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门