Linq 查询给出不适当的输出

本文关键字:输出 不适当 查询 Linq | 更新日期: 2023-09-27 18:29:58

我有两个事务表,分别名为ParentTransaction和ChildTransaction,其中ParentTransactionTransactionId将充当TransactionIdChildTransaction的外来项。

现在我想获取所有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。

Linq 查询给出不适当的输出

查询语言的一般思想是表达所需的结果,而不是如何获得它。

将其应用于方案会导致如下所示的简单查询

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 查询优化器会(并且正在(为我们完成这项工作,因此我们不需要考虑是否需要使用 joinsubquery 等。

我不确定!=是否是最佳值。以下是具有>检查和分组的解决方案:

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

可以按照声明方式读取此查询,如下一个要求:

  1. TransactionId对子事务进行分组集合,并为每个组检索一个匿名类型对象,其中包含字段 TransactionId = 分组键 (== TransactionId( 和 Deposit,后者是具有相同 TransactionId 的行的存款总和。
  2. 将第 1 部分的集合连接到表PaerntTransaction TransactionId字段。对于每个联接的对,检索一个匿名类型对象,其中包含字段 交易 == 来自表ParentTransactions交易和存款,这是来自第 1 部分集合的存款,这是来自ChildTransactions表中具有相同TransactionId的存款的总和。
  3. 仅从结果集中筛选 PayAmount 大于存款总和的对象。
  4. 仅返回每个筛选行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);