本文深入探讨了在laravel中如何优雅地实现父模型(如客户)基于其“has one of many”关系(如最新联系记录)进行排序的需求。面对直接关联查询可能导致数据重复的问题,文章提出了利用子查询连接(subquery join)作为高效且简洁的解决方案,详细阐述了如何构建子查询来聚合相关数据,并将其与主模型连接,最终实现精确的排序。
在Laravel应用开发中,我们经常会遇到需要根据关联模型的特定属性来排序主模型的情况。例如,一个Customer(客户)模型可能拥有多个Contact(联系记录),我们希望能够根据每个客户的“最新联系时间”来对客户列表进行排序。
Laravel的“Has One Of Many”关系(在Laravel 8+中引入)是解决此类问题的强大工具。它允许我们轻松地定义一个关系,以获取多个相关模型中符合特定条件(如最大值、最小值)的单个模型。
模型定义示例:
假设我们有Customer和Contact两个模型,Customer拥有多个Contac
t。我们定义一个latestContact关系来获取每个客户的最新联系记录。
// app/Models/Customer.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
use HasFactory;
public function contacts()
{
return $this->hasMany(Contact::class);
}
// 定义获取最新联系记录的关系
public function latestContact()
{
return $this->hasOne(Contact::class)->ofMany('contacted_at', 'max')->withDefault();
}
}// app/Models/Contact.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Contact extends Model
{
use HasFactory, SoftDeletes;
protected $casts = [
'contacted_at' => 'datetime',
];
public function customer()
{
return $this->belongsTo(Customer::class);
}
}在Contact模型的迁移文件中,contacted_at字段用于记录联系时间:
// database/migrations/..._create_contacts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateContactsTable extends Migration
{
public function up()
{
Schema::create('contacts', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->softDeletes();
$table->foreignId('customer_id')->constrained()->onDelete('cascade');
$table->string('type');
$table->dateTime('contacted_at'); // 用于排序的关键字段
});
}
public function down()
{
Schema::dropIfExists('contacts');
}
}我们的目标是获取所有客户,并根据他们的最新联系时间进行排序。直观上,可能会尝试使用join语句将customers表与contacts表连接起来,然后按contacted_at排序。
// 尝试一:直接JOIN (会导致重复数据)
$query = Customer::select('customers.*', 'contacts.contacted_at as latest_contact_at')
->join('contacts', 'customers.id', '=', 'contacts.customer_id')
->orderBy('contacts.contacted_at', 'desc')
->get();这种方法的问题在于,如果一个客户有多个联系记录,join操作会为每个联系记录生成一行客户数据,导致客户信息重复,这并非我们所期望的结果。我们需要的是每个客户只出现一次,并且其排序依据是其“最新”的联系时间。
解决此问题的最优雅和高效的方式是使用Laravel的子查询连接(Subquery Join)。这种方法允许我们首先在一个子查询中聚合所需的关联数据(即每个客户的最新联系时间),然后将这个聚合结果作为一个临时表与主表进行连接。
核心思想:
实现代码:
use Illuminate\Support\Facades\DB;
$latestContactsSubquery = Contact::select('customer_id', DB::raw('max(contacted_at) as latest_contact_at'))
->groupBy('customer_id');
$customers = Customer::select('customers.*', 'latest_contacts.latest_contact_at')
->joinSub($latestContactsSubquery, 'latest_contacts', function ($join) {
$join->on('customers.id', '=', 'latest_contacts.customer_id');
})
->orderBy('latest_contacts.latest_contact_at', 'desc')
->get();代码解析:
$latestContactsSubquery:
Customer::select(...):
ALTER TABLE contacts ADD INDEX (customer_id); ALTER TABLE contacts ADD INDEX (contacted_at);
或者在迁移文件中添加:
$table->foreignId('customer_id')->constrained()->onDelete('cascade')->index();
$table->dateTime('contacted_at')->index();$customers = Customer::select('customers.*', 'latest_contacts.latest_contact_at')
->joinSub($latestContactsSubquery, 'latest_contacts', function ($join) {
$join->on('customers.id', '=', 'latest_contacts.customer_id');
})
->orderBy('latest_contacts.latest_contact_at', 'desc')
->with('latestContact') // 如果需要访问关系数据
->get();请注意,with('latestContact')会产生额外的查询来加载每个客户的最新联系记录,这与我们通过子查询获取latest_contact_at是不同的目的。子查询用于排序,而with用于加载完整的关联模型对象。
通过利用Laravel的子查询连接功能,我们可以优雅且高效地解决根据“Has One Of Many”关系对父模型进行排序的复杂需求。这种方法避免了直接JOIN可能导致的重复数据问题,保持了查询的清晰性和结果的准确性。在处理类似需要聚合关联数据进行排序的场景时,子查询连接是一个非常推荐的解决方案。
# php
# laravel
# cad
# app
# 工具
# ai
# 应用开发
# select
# 闭包
# function
# 对象
# 多个
# 是一个
# 加载
# 会为
# 这种方法
# 查询结果
# 的是
# 这是
# 他们的
# 这一
相关文章:
ui设计制作网站有哪些,手机UI设计网址吗?
如何高效完成自助建站业务培训?
西安市网站制作公司,哪个相亲网站比较好?西安比较好的相亲网站?
php条件判断怎么写_ifelse和switchcase的使用区别【对比】
C++时间戳转换成日期时间的步骤和示例代码
建站OpenVZ教程与优化策略:配置指南与性能提升
建站之星与建站宝盒如何选择最佳方案?
手机网站制作与建设方案,手机网站如何建设?
微信h5制作网站有哪些,免费微信H5页面制作工具?
广东专业制作网站有哪些,广东省能源集团有限公司官网?
建站之星价格显示格式升级,你的预算足够吗?
c# 在高并发场景下,委托和接口调用的性能对比
商务网站制作工程师,从哪几个方面把握电子商务网站主页和页面的特色设计?
韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐
如何用VPS主机快速搭建个人网站?
云南网站制作公司有哪些,云南最好的招聘网站是哪个?
枣阳网站制作,阳新火车站打的到仙岛湖多少钱?
MySQL查询结果复制到新表的方法(更新、插入)
网站制作说明怎么写,简述网页设计的流程并说明原因?
网站制作公司,橙子建站是合法的吗?
微信小程序 input输入框控件详解及实例(多种示例)
XML的“混合内容”是什么 怎么用DTD或XSD定义
Android滚轮选择时间控件使用详解
定制建站模板如何实现SEO优化与智能系统配置?18字教程
如何获取PHP WAP自助建站系统源码?
如何挑选高效建站主机与优质域名?
西安制作网站公司有哪些,西安货运司机用的最多的app或者网站是什么?
c++23 std::expected怎么用 c++优雅处理函数错误返回【详解】
无锡营销型网站制作公司,无锡网选车牌流程?
制作假网页,招聘网的薪资待遇,会有靠谱的吗?一面试又各种折扣?
高性能网站服务器部署指南:稳定运行与安全配置优化方案
建站之星安装模板失败:服务器环境不兼容?
深圳企业网站制作设计,在深圳如何网上全流程注册公司?
浅析上传头像示例及其注意事项
C++如何将C风格字符串(char*)转换为std::string?(代码示例)
成都品牌网站制作公司,成都营业执照年报网上怎么办理?
建站主机是否属于云主机类型?
网站制作培训多少钱一个月,网站优化seo培训课程有哪些?
网站制作大概要多少钱一个,做一个平台网站大概多少钱?
网站好制作吗知乎,网站开发好学吗?有什么技巧?
成都响应式网站开发,dw怎么把手机适应页面变成网页?
简历在线制作网站免费版,如何创建个人简历?
如何在IIS7上新建站点并设置安全权限?
c++怎么使用类型萃取type_traits_c++ 模板元编程类型判断【方法】
宿州网站制作公司兴策,安徽省低保查询网站?
建站之星上传入口如何快速找到?
简易网站制作视频教程,使用记事本编写一个简单的网页html文件?
如何通过商城自助建站源码实现零基础高效建站?
设计网站制作公司有哪些,制作网页教程?
如何快速查询网址的建站时间与历史轨迹?
*请认真填写需求信息,我们会在24小时内与您取得联系。