【问题标题】:Linq query giving inappropriate outputLinq 查询给出不适当的输出
【发布时间】:2016-01-12 06:39:28
【问题描述】:

我有两个名为 ParentTransactionChildTransaction 的事务表,其中 ParentTransactionTransactionId 将作为 ChildTransaction 的外部对象TransactionId

现在我想获取所有 payamount 未完成的 ParentTransaction 的 TransactionId。

从下面的输出我想要交易 ID 3 的记录,因为transactionid 3 只支付了 1000 而不是 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 1 的 600 和 400。

【问题讨论】:

    标签: c# asp.net linq linq-group


    【解决方案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 Martynov 的回答相比,上述内容效率低下。嗯,可能是,可能不是。 Vadim 正在尝试强制执行特定的执行计划,我可以理解 - 我们必须在实际遇到查询性能问题时执行此类操作。但这不是自然的,只有在我们遇到性能问题时才应该是最后的手段。在大多数情况下,查询提供程序和 SQL 查询优化器将为我们完成(并且正在完成)这项工作,因此我们不需要考虑是否需要使用 joinsubquery 等。

    【讨论】:

    • 你能给我看看这个的 sql 等效查询吗
    • @Learning 添加 var sql = query.ToString();,在 VS 调试器中运行,设置断点并使用 Text Visualizer 在 Locals/Watch 窗口中检查 sql 变量。
    • 你看过 vadim 查询吗?你能告诉我一些关于它的事情吗,因为我对他的查询一无所知
    • 是的,我做到了。这是产生相同结果的正确和替代方法。但这是我帖子的重点,当查看查询时,很难理解它在做什么。您可以使用 Intellisense 和 MSDN 文档来理解它,但基本上它会创建一个子查询,将子事务按TrasactionId 分组并计算DebitAmout 的总和,然后将该子查询连接到ParentTransaction 表以获得最终结果。
    • 是的,他的查询是正确的,但真的很难理解他在做什么是他的查询
    【解决方案2】:

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

    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. GroupTransactionId 的子交易集合,并为每个组检索一个 anonymous type 对象,其中包含字段 TransactionId = 分组键 (== TransactionId) 和 Deposit,这是具有相同 TransactionId 的行的存款总和。
    2. Join 从第 1 部分设置到表 PaerntTransactionTransactionId 字段。对于每个连接对,检索一个 anonymous type 对象,其中包含来自ParentTransactions 表的字段 Transaction == 事务和来自第 1 部分集合的存款,它是来自ChildTransactions 表的具有相同 TransactionId 的存款的总和。李>
    3. 仅从结果集中过滤 PayAmount 大于存款总和的对象。
    4. 仅返回每个过滤行的 ParentTransaction 对象。

    这是 SQL 优化的场景,因为连接、过滤和分组会阻止 nested queries 在其他情况下添加到实际执行计划中并降低性能。

    更新

    要解决没有存款的交易问题,您可以使用LEFT JOIN

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

    现在您检索所有父事务并将其与子事务加入,但如果没有子事务,则使用Deposit == 0 并通过ParentTransaction 以类似方式对加入的实体进行分组。

    【讨论】:

    • 你能解释一下你的查询吗?请
    • 非常感谢先生的一个糟糕的查询。我真的很难做到这一点。甚至不能感谢你,因为感谢你会太小了。
    • @Learning 是的,有一个问题。我用左连接示例更新了我的答案,这是一个指向 MSDN 的额外链接。
    • @Learning 当然,只需将查询中的 WHERE 表达式更改为 @t => @t.g.Transaction.PayAmount == @t.g.Deposit@t.g.Transaction.PayAmount <= @t.g.Deposit
    • 不错的面向集合的解决方案。 IMO,在创建 SQL 查询时,当您考虑过滤/组合记录集时,您会得到更好的结果,而不是考虑单个记录的所有条件(如果您明白我的意思)。而且我认为有些人认为查询优化器会自己发现将面向记录的查询转换为面向集合的查询(即,将嵌套查询更改为连接)是相当乐观的。 (但关于最后一点,我愿意被任何有知识的人纠正。)
    【解决方案3】:

    问题

    问题在于这句话:

    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而不是单个孩子的DepositAmount的总和值,小于父级中的PayAmount,然后

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

    【讨论】:

    • 你能告诉我你的查询和 D Mayuri 查询有什么区别吗,因为我已经尝试过 D Mayuri 查询,但它不起作用
    • @Learning 我没试过 D. Mayuri。但是我已经尝试了自己的查询,并且效果很好。给我一点时间,我试试看。同时,你也可以试试我的……
    • 是的,我肯定会尝试您的查询,但请考虑我在 Vadim 答案中描述的情况。请参阅我在 vadim 答案上的 cmets
    • @Learning 啊,我明白了。您还想获得根本没有报酬的交易4...我将更新我的答案..
    • 我只想得到所有未付款的交易。
    【解决方案4】:
    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();
    }
    

    【讨论】:

    • 非常感谢您为帮助我所做的努力。
    【解决方案5】:

    您只控制一个子事务。您必须使用Sum() 操作并且需要使用&gt; 而不是!= 请试试这个。

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

    【讨论】:

    • 非常感谢您为帮助我所做的努力。
    • 没问题,伙计。我希望这个答案对你有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2013-01-14
    • 2016-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多