Laravel基于关联关系实现三表联查 获取支付关联的客户信息
问题核心定位
原代码存在三处关键逻辑偏差,导致无法正确关联获取客户信息:
- 手写join逻辑错误:尝试关联customers表时,错误将
sales_payments.invoice_id与customers.customer_id做等值匹配,完全不符合逐级外键的关联链路 - 模型关联定义错误:在SalesPayments模型中编写的
hasManyThrough关联参数顺序完全和表结构不匹配,混淆了各表的外键对应关系 - 关联类型选择错误:从表结构看,单条支付记录对应单张发票、单张发票对应单个客户,属于一对一跨表关联,应该使用
hasOneThrough而非hasManyThrough
分步修正实现
1. 补全模型间的基础关联
按照外键链路补全各模型的原生关联,作为跨表关联的基础:
- 在
SalesPayments模型中定义与发票的关联:
// app/Models/SalesPayments.php public function salesInvoice() { return $this->belongsTo(SalesInvoices::class, 'invoice_id', 'invoice_id'); }
- 在
SalesInvoices模型中定义与客户的关联:
// app/Models/SalesInvoices.php public function customer() { return $this->belongsTo(Customers::class, 'customer_id', 'customer_id'); }
- 在
SalesPayments模型中定义跨表直连客户的关联(修正原有错误定义):
// app/Models/SalesPayments.php public function customer() { // 严格按照hasOneThrough参数顺序传入对应表的关联字段 return $this->hasOneThrough( Customers::class, SalesInvoices::class, 'invoice_id', // 中间表sales_invoices与当前表sales_payments关联的外键 'customer_id', // 最终表customers与中间表sales_invoices关联的外键 'invoice_id', // 当前表sales_payments与中间表关联的本地外键 'invoice_id' // 中间表sales_invoices的主键 ); }
2. 查询实现方案(两种可选)
方案A:Eloquent预加载(Laravel规范推荐)
无需手写原生join逻辑,通过预加载机制自动关联数据,避免字段冲突:
$payments = SalesPayments::whereNotNull('payment_amount') ->with([ 'salesInvoice.customer', // 逐级预加载发票、客户信息 'paymentOption' // 保留原有逻辑关联支付选项 ]) ->get();
调用时可直接通过两种方式获取客户信息:
- 嵌套取值:
$payment->salesInvoice->customer - 直连取值(依赖上面定义的hasOneThrough关联):
$payment->customer
小优化:原代码中where('payment_amount', '!=' , NULL) 可以替换为语义更严谨的whereNotNull('payment_amount'),兼容不同数据库对空值的判断逻辑。
方案B:修正后的手写join查询
如果要保留手写join的写法,需要修正关联字段的匹配逻辑:
$payments = SalesPayments::whereNotNull('sales_payments.payment_amount') ->join('sales_invoices', 'sales_invoices.invoice_id', '=', 'sales_payments.invoice_id') ->join('payment_options', 'payment_options.paymentoption_id', '=', 'sales_payments.paymentoption_id') // 修正关联逻辑:通过发票表的customer_id关联客户表,而非直接关联支付表 ->join('customers', 'customers.customer_id', '=', 'sales_invoices.customer_id') // 手动指定查询字段,避免同名字段覆盖 ->select('sales_payments.*', 'sales_invoices.*', 'customers.*', 'payment_options.*') ->get();
内容的提问来源于stack exchange,提问作者rjcode
相关产品推荐
相关产品推荐

