数据库一对多关系查询

时间:2021-02-28 11:15:39

标签: mysql sql database laravel

我的数据库中有 3 个表;交易、交易明细和账户 - 基本上如下。

交易:

  • id
  • 详情
  • by_user
  • created_at

trans_details :

  • id
  • trans_id(外键)
  • account_id
  • account_type (Enum -[c,d])
  • 金额

帐户:

  • id
  • 子名称

在每笔交易中,每个账户可能是债权人或债务人。我想要的是一个账户对账单(例如:银行账户变动),所以我需要在账户类型 = c(债权人)或账户类型 = d(债务人)时查询每个移动

trans_id、金额、created_at、creditor_account、debor_account

更新:我尝试了以下查询,但我得到的债务人列值全部为空!

SELECT transactions.created_at,trans_details.amount,(case WHEN trans_details.type = 'c' THEN sub_account.sub_name END) as creditor,
(case WHEN trans_details.type = 'd' THEN sub_account.sub_name END) as debtor from transactions
JOIN trans_details on transactions.id = trans_details.trans_id
JOIN sub_account on trans_details.account_id = sub_account.id
GROUP by transactions.id 

在@Jalos 的帮助下,我不得不将查询转换为 Laravel,这也让我多花了 2 个小时来转换并获得正确的结果:) 下面是 Laravel 代码,以防有人需要执行此类查询 我还添加了两个日期之间的功能

 public function accountStatement($from_date,$to_date)
    {
        $statemnt = DB::table('transactions')
        ->Join('trans_details as credit_d',function($join) {
                            $join->on('credit_d.trans_id','=','transactions.id');
                            $join->where('credit_d.type','c');
                        })
        ->Join('sub_account as credit_a','credit_a.id','=','credit_d.account_id')
        ->Join('trans_details as debt_d',function($join) {
                            $join->on('debt_d.trans_id','=','transactions.id');
                            $join->where('debt_d.type','d');
                        })
        ->Join('sub_account as debt_a','debt_a.id','=','debt_d.account_id')
        ->whereBetween('transactions.created_at',[$from_date,$to_date])
        ->select('transactions.id','credit_d.amount','transactions.created_at','credit_a.sub_name as creditor','debt_a.sub_name as debtor')
        ->get();
        return response()->json(['status_code'=>2000,'data'=>$statemnt , 'message'=>''],200);
    }

1 个答案:

答案 0 :(得分:1)

您的 transactions 表表示交易记录,而您的 accounts 表表示帐户记录。您的 trans_details 表表示 transactionsaccounts 之间的链接。因此,由于在交易中存在债权人和债务人,因此我假设 trans_details 每笔交易恰好有两条记录:

select transactions.id, creditor_details.amount, transactions.created_at, creditor.sub_name, debtor.sub_name
from transactions
join trans_details creditor_details
on transactions.id = creditor_details.trans_id and creditor_details.account_type = 'c'
join accounts creditor
on creditor_details.account_id = creditor.id
join trans_details debtor_details
on transactions.id = debtor_details.trans_id and debtor_details.account_type = 'd'
join accounts debtor
on debtor_details.account_id = debtor.id;

enter image description here

编辑

正如承诺的那样,我正在调查您编写的查询。它看起来像这样:

SELECT transactions.id,trans_details.amount,(case WHEN trans_details.type = 'c' THEN account.name END) as creditor,
(case WHEN trans_details.type = 'd' THEN account.name END) as debtor from transactions
JOIN trans_details on transactions.id = trans_details.trans_id
JOIN account on trans_details.account_id = account.id
GROUP by transactions.id

这几乎是正确的。问题是由于 group-by MySQL 只能为债权人和债务人的每条记录显示一个值。但是,我们知道这两者恰好有两个值:当您与 Deboror 匹配时,creditor 有一个 null 值,当您与 creditor 匹配时,有一个适当的 creditor 值。债务人的情况类似。我对这个查询的期望是 MySQL 会抛出一个错误,因为你没有按这些计算的 case-when 字段分组,然而,有几个值,但似乎 MySQL 在这么多年后会让我感到惊讶 :)

从结果中我们看到 MySQL 可能找到了第一个值并将其用于债权人和债务人。由于它遇到了债权人匹配作为第一次匹配,因此它具有正确的 creditor 值和 null debtor 值。但是,如果您编写防弹代码,您将永远不会遇到这些奇怪的行为。在我们的案例中,对您的代码进行一些极简改进会将其转换为防弹版本并提供正确的结果:

SELECT transactions.id,trans_details.amount,max((case WHEN trans_details.type = 'c' THEN account.name END)) as creditor,
max((case WHEN trans_details.type = 'd' THEN account.name END)) as debtor from transactions
JOIN trans_details on transactions.id = trans_details.trans_id
JOIN account on trans_details.account_id = account.id
group by transactions.id

请注意,我对您的代码所做的唯一更改是在 case-when 定义周围包装 max() 函数调用,因此我们避免使用 null 值,因此您的方法非常接近防弹解决方案。

小提琴:http://sqlfiddle.com/#!9/d468dc/10/0

enter image description here

然而,即使你的思维过程在理论上是正确的(理论上理论和实践没有区别,但在实践中它们通常是不同的)并且一些细微的变化正在将其转化为运行良好的代码,我仍然更喜欢我的查询,因为它避免了 group by 子句,这在必要时很有用,但这里没有必要做 group by,这在性能、内存使用方面可能更好,更容易阅读和保持为您未来的定制提供更多选择。然而,您的尝试非常接近解决方案。

关于我的查询,我使用的技巧是对相同的表进行多次连接,给它们取别名,然后将它们区分开来,就好像它们是不同的表一样。这是一个非常有用的技巧,你将来会需要很多。