Linq 查询给出不适当的输出
本文关键字:输出 不适当 查询 Linq | 更新日期: 2023-09-27 18:29:58
我有两个事务表,分别名为ParentTransaction和ChildTransaction,其中ParentTransaction的TransactionId
将充当TransactionId
的ChildTransaction的外来项。
现在我想获取所有payamount
未完成的父交易的事务ID。
从下面的输出中,我想要交易 ID 3 的记录,因为只有 1000 支付了 transactionid
3 而不是 5000。
我有一张这样的表:
Transactionid(p.k) PayAmount
1 1000
2 3000
3 5000
4 6000
子交易
Id TransactionId(F.k) DepositAmount
1 1 600
2 1 400
3 2 1000
4 2 1000
5 2 1000
6 3 2000
这是我的查询:
var data = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction on tmp.Transactionid equals
tmp1.Transactionid where tmp.PayAmount !=tmp1.DepositAmount
select tmp);
但是在这里我得到了交易 ID 1 和 2,尽管他们的交易已经分两部分完成,即交易 ID 为 600 和 400 1。
查询语言的一般思想是表达所需的结果,而不是如何获得它。
将其应用于方案会导致如下所示的简单查询
var query = context.ParentTransaction
.Where(t => t.PayAmount != context.ChildTransaction
.Where(ct => ct.TransactionId == t.TransactionId)
.Sum(ct => ct.DepositAmount));
如果使用 EF 和适当的模型导航属性,则甚至会很简单
var query = context.ParentTransaction
.Where(t => t.PayAmount != t.ChildTransactions.Sum(ct => ct.DepositAmount));
有人可能会说,与马丁诺夫回答@Vadim相比,上述内容效率低下。嗯,可能是的,也许不是。瓦迪姆试图强制实施特定的执行计划,我可以理解 - 当现实中遇到查询性能问题时,我们必须做这样的事情。但这并不自然,只有在我们遇到性能问题时才应该是最后的手段。在大多数情况下,查询提供程序和 SQL 查询优化器会(并且正在(为我们完成这项工作,因此我们不需要考虑是否需要使用 join
与 subquery
等。
我不确定!=
是否是最佳值。以下是具有>
检查和分组的解决方案:
var expectedValue =
context.ChildTransaction
.GroupBy(t => t.TransactionId, (key, group) => new { TransactionId = key, Deposit = group.Sum(e => e.Deposit) })
.Join(context.ParentTransaction, grouped => grouped.TransactionId, transaction => transaction.TransactionId, (group, transaction) => new { Transaction = transaction, group.Deposit })
.Where(result => result.Transaction.PayAmount > result.Deposit)
.Select(result => result.Transaction);
可以按照声明方式读取此查询,如下一个要求:
- 按
TransactionId
对子事务进行分组集合,并为每个组检索一个匿名类型对象,其中包含字段 TransactionId = 分组键 (== TransactionId( 和 Deposit,后者是具有相同 TransactionId 的行的存款总和。 - 将第 1 部分的集合连接到表
PaerntTransaction
TransactionId
字段。对于每个联接的对,检索一个匿名类型对象,其中包含字段 交易 == 来自表ParentTransactions
交易和存款,这是来自第 1 部分集合的存款,这是来自ChildTransactions
表中具有相同TransactionId
的存款的总和。 - 仅从结果集中筛选 PayAmount 大于存款总和的对象。
- 仅返回每个筛选行
ParentTransaction
对象。
这是 SQL 优化的方案,因为联接、筛选和分组会阻止嵌套查询,这些查询可以在其他情况下添加到实际执行计划中,并降低性能。
更新
要解决没有存款的交易问题,您可以使用左联接:
var expectedValue = from parent in context.ParentTransaction
join child in context.ChildTransaction on parent.TransactionId equals child.TransactionId into gj
from subset in gj.DefaultIfEmpty()
let joined = new { Transaction = parent, Deposit = subset != null ? subset.Deposit : 0 }
group joined by joined.Transaction
into grouped
let g = new { Transaction = grouped.Key, Deposit = grouped.Sum(e => e.Deposit) }
where g.Transaction.PayAmount > g.Deposit
select g.Transaction;
使用 LINQ 方法链进行相同的查询:
var expectedValue =
context.ParentTransaction
.GroupJoin(context.ChildTransaction, parent => parent.TransactionId, child => child.TransactionId, (parent, gj) => new { parent, gj })
.SelectMany(@t => @t.gj.DefaultIfEmpty(), (@t, subset) => new { @t, subset })
.Select(@t => new { @t, joined = new { Transaction = @t.@t.parent, Deposit = @t.subset != null ? @t.subset.Deposit : 0 } })
.GroupBy(@t => @t.joined.Transaction, @t => @t.joined)
.Select(grouped => new { grouped, g = new { Transaction = grouped.Key, Deposit = grouped.Sum(e => e.Deposit) } })
.Where(@t => @t.g.Transaction.PayAmount > @t.g.Deposit)
.Select(@t => @t.g.Transaction);
现在,您检索所有父事务并将其与子事务联接,但是如果没有子事务,则通过ParentTransaction
以类似的方式使用Deposit == 0
和组加入的实体。
问题
问题在于这个声明:
where tmp.PayAmount != tmp1.DepositAmount //the culprit
由于tmp1
被定义为单个子事务,因此该语句将导致等同于错误的值:
展示台:
1000 != 600 //(result: true -> selected) comparing parent 1 and child 1
1000 != 400 //(result: true -> selected) comparing parent 1 and child 2
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 3
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 4
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 5
5000 != 2000 //(result: true -> selected) comparing parent 2 and child 5
//However, you do not want it to behave like this actually
但你想要的是:
展示台:
1000 != (600 + 400) //(result: false -> not selected) comparing parent 1 and child 1 & 2, based on the TransactionId
3000 != (1000 + 1000 + 1000) //(result: false -> not selected) comparing parent 2 and child 3, 4, & 5, based on the TransactionId
5000 != (2000) //(result: true -> selected) comparing parent 3 and child 6, based on the TransactionId
6000 != nothing paid //(result: true -> selected) comparing parent 3 with the whole childTransaction and found there isn't any payment made
因此,您应该tmp1
作为孩子的集合而不是单个孩子。
溶液
未付款交易
像这样更改代码:
var data = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction.GroupBy(x => x.TransactionId) //group this by transaction id
on tmp.TransactionId equals tmp1.Key //use the key
where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount) //get the sum of the deposited amount
select tmp)
.Union( //added after edit
(from tmp in context.ParentTransaction
where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
select tmp)
);
解释
这一行:
join tmp1 in context.ChildTransaction.GroupBy(x => x.TransactionId) //group this by transaction id
利用Linq
中的GroupBy
,这条线使tmp1
一组子项而不是单个子项,并且理所当然地基于其外键,即TransactionId
。
然后这一行:
on tmp.TransactionId equals tmp1.Key //use the key
我们只是将tmp.TransactionId
等同于儿童组密钥tmp1.Key
然后是下一行:
where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount) //get the sum of the deposited amount
获取子项DepositAmount
的总和值,而不是小于父项PayAmount
的单个子项DepositAmount
,然后
select tmp
选择满足上述所有条件的所有父交易。这样,我们就完成了一半。
下一步是考虑在父项中发生的事务,但不发生在子项中。这也被认为是无偿的。
我们可以使用 Union
将第一个query
的结果与第二个query
结合起来
.Union( //added after edit
(from tmp in context.ParentTransaction
where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
select tmp)
);
这将选择父事务中存在但子事务中根本不存在的任何内容(因此被视为未付款(。
您将获得正确的data
,包括未全额支付的ParentTransaction
行,无论是TransactionId
是否存在于子交易中的父交易。
付费交易
至于付费交易,只需将查询从>
更改为<=
:
var datapaid = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction.GroupBy(y => y.TransactionId)
on tmp.TransactionId equals tmp1.Key
where tmp.PayAmount <= tmp1.Sum(x => x.DepositAmount)
select tmp);
组合的
我们可以像这样进一步简化上面的查询:
var grp = context.ChildTransaction.GroupBy(y => y.TransactionId);
var data = (from tmp in context.ParentTransaction
join tmp1 in grp //group this by transaction id
on tmp.TransactionId equals tmp1.Key //use the key
where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount)
select tmp)
.Union((
from tmp in context.ParentTransaction
where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
select tmp));
var datapaid = (from tmp in context.ParentTransaction
join tmp1 in grp
on tmp.TransactionId equals tmp1.Key
where tmp.PayAmount <= tmp1.Sum(x => x.DepositAmount)
select tmp);
List<int> obj = new List<int>();
using (DemoEntities context = new DemoEntities())
{
obj = (from ct in context.CTransactions
group ct by ct.Transactionid into grp
join pt in context.PTransactions on grp.Key equals pt.Transactionid
where grp.Sum(x => x.DepositAmount) < pt.PayAmount
select grp.Key).ToList();
}
您只能控制一个子事务。您必须使用Sum()
操作,并且需要使用>
而不是!=
请尝试此操作。
var data = (from tmp in context.ParentTransaction
join tmp1 in context.ChildTransaction on tmp.Transactionid equals into tmp1List
tmp1.Transactionid where tmp.PayAmount > tmp1List.Sum(l => l.DepositAmount)
select tmp);