【问题标题】:How do you inner join, left join AND full join in one query?您如何在一个查询中进行内连接、左连接和完全连接?
【发布时间】:2018-04-28 20:17:42
【问题描述】:

我正在用 C# 编写一个 Web api,并且在我的 get 方法中有一个复杂的 SQL 查询要做。我从一位同事(他现在不在这里或无法访问)那里收到了一个 SQL 文件,其中包含以下内容:

FROM orsrg ors WITH (NOLOCK)
 INNER JOIN orkrg ork WITH (NOLOCK) ON ork.ordernr = ors.ordernr
 LEFT JOIN cicmpy c1 WITH (NOLOCK) ON ork.debnr = c1.debnr
 LEFT JOIN cicmpy c2 WITH (NOLOCK) ON ork.fakdebnr = c2.debnr
 LEFT JOIN cicmpy c3 WITH (NOLOCK) ON ork.verzdebnr = c3.debnr
 FULL JOIN humres h with (nolock) on h.res_id = ork.represent_id

到目前为止,这是我为此拥有的 C# 代码:

// This "using" is used to make the tables in the SQL query run as "WITH (NOLOCK)" ! (IsolationLevel.ReadUncommitted)

using (var txn = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions
    {
        IsolationLevel = IsolationLevel.ReadUncommitted
    }
))
{
    // Your LINQ to SQL query goes here
    var query = (from ors in _context.Orsrg
                 join ork in _context.Orkrg on ors.Ordernr equals ork.Ordernr
                 join c1 in _context.Cicmpy.DefaultIfEmpty() on ork.Debnr equals c1.Debnr
                 join c2 in _context.Cicmpy.DefaultIfEmpty() on ork.Fakdebnr equals c2.Debnr
                 join c3 in _context.Cicmpy.DefaultIfEmpty() on ork.Verzdebnr equals c3.Debnr
                 // FULL JOIN MUST COME HERE
                 );
}

如您所见,我已经对表 ors 和 ork 进行了内部连接,然后在表 c1、c2、c3 上进行了三个左连接(尽管都来自 Cicmpy)。所以剩下的就是完全连接。

我在 stackoverflow 上也找到了以下代码(这不是我的代码):

var firstNames = new[]
{
    new { ID = 1, Name = "John" },
    new { ID = 2, Name = "Sue" },
};
var lastNames = new[]
{
    new { ID = 1, Name = "Doe" },
    new { ID = 3, Name = "Smith" },
};
var leftOuterJoin = from first in firstNames
                    join last in lastNames
                    on first.ID equals last.ID
                    into temp
                    from last in temp.DefaultIfEmpty(new { first.ID, Name = default(string) })
                    select new
                    {
                        first.ID,
                        FirstName = first.Name,
                        LastName = last.Name,
                    };
var rightOuterJoin = from last in lastNames
                     join first in firstNames
                     on last.ID equals first.ID
                     into temp
                     from first in temp.DefaultIfEmpty(new { last.ID, Name = default(string) })
                     select new
                     {
                         last.ID,
                         FirstName = first.Name,
                         LastName = last.Name,
                     };
var fullOuterJoin = leftOuterJoin.Union(rightOuterJoin); 

该代码通过将其分成两半进行完全外连接,一旦他进行左外连接,一次右外连接,然后将它们放在一个变量中。

我不知道如何在我原来的 C# 代码中做到这一点,现在它说

// FULL JOIN MUST COME HERE

这些 SQL 查询比在学校要难很多...

【问题讨论】:

标签: c# sql-server linq join


【解决方案1】:

一些(有些)复杂的扩展方法将实现AsQueryable 的完整外连接并转换为 SQL,但可能不如直接使用 FULL OUTER JOIN 调用 SQL 高效。

首先是扩展(这里的另一个答案中IEnumerable 有一些变化):

private static Expression<Func<TP, TC, TResult>> CastSMBody<TP, TC, TResult>(LambdaExpression ex, TP unusedP, TC unusedC, TResult unusedRes) => (Expression<Func<TP, TC, TResult>>)ex;

public static IQueryable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
    this IQueryable<TLeft> leftItems,
    IQueryable<TRight> rightItems,
    Expression<Func<TLeft, TKey>> leftKeySelector,
    Expression<Func<TRight, TKey>> rightKeySelector,
    Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class {

    var sampleAnonLR = new { left = (TLeft)null, rightg = (IEnumerable<TRight>)null };
    var parmP = Expression.Parameter(sampleAnonLR.GetType(), "p");
    var parmC = Expression.Parameter(typeof(TRight), "c");
    var argLeft = Expression.PropertyOrField(parmP, "left");
    var newleftrs = CastSMBody(Expression.Lambda(Expression.Invoke(resultSelector, argLeft, parmC), parmP, parmC), sampleAnonLR, (TRight)null, (TResult)null);

    return leftItems.AsQueryable().GroupJoin(rightItems, leftKeySelector, rightKeySelector, (left, rightg) => new { left, rightg }).SelectMany(r => r.rightg.DefaultIfEmpty(), newleftrs);
}

private static Expression<Func<TParm, TResult>> CastSBody<TParm, TResult>(LambdaExpression ex, TParm unusedP, TResult unusedRes) => (Expression<Func<TParm, TResult>>)ex;

public static IQueryable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
    this IQueryable<TLeft> leftItems,
    IQueryable<TRight> rightItems,
    Expression<Func<TLeft, TKey>> leftKeySelector,
    Expression<Func<TRight, TKey>> rightKeySelector,
    Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class {

    var sampleAnonLgR = new { leftg = (IEnumerable<TLeft>)null, right = (TRight)null };
    var parmLgR = Expression.Parameter(sampleAnonLgR.GetType(), "lgr");
    var argLeft = Expression.Constant(null, typeof(TLeft));
    var argRight = Expression.PropertyOrField(parmLgR, "right");
    var newrightrs = CastSBody(Expression.Lambda(Expression.Invoke(resultSelector, argLeft, argRight), parmLgR), sampleAnonLgR, (TResult)null);

    return rightItems.GroupJoin(leftItems, rightKeySelector, leftKeySelector, (right, leftg) => new { leftg, right }).Where(lgr => !lgr.leftg.Any()).Select(newrightrs);
}

public static IQueryable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
    this IQueryable<TLeft> leftItems,
    IQueryable<TRight> rightItems,
    Expression<Func<TLeft, TKey>> leftKeySelector,
    Expression<Func<TRight, TKey>> rightKeySelector,
    Expression<Func<TLeft, TRight, TResult>> resultSelector) where TLeft : class where TRight : class where TResult : class {

    return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}

然后您可以创建您的查询。我不得不修改它,因为您的 LEFT OUTER JOIN 仿真不正确。另外,我必须输入一个选择,因为它们没有完全外连接的查询理解语法,而且我不想将查询的其余部分转换为 lambda 语法。

注意:由于query 变量的创建实际上并没有调用数据库,因此您的事务不会执行您想要的操作。您需要做一些事情来从 query 中提取事务的结果(NOLOCK 解决方法)以影响 SQL 上的查询。

var query = (from ors in _context.Orsrg
             join ork in _context.Orkrg on ors.Ordernr equals ork.Ordernr
             join c1 in _context.Cicmpy on ork.Debnr equals c1.Debnr into c1j
             from c1 in c1j.DefaultIfEmpty()
             join c2 in _context.Cicmpy on ork.Fakdebnr equals c2.Debnr into c2j
             from c2 in c2j.DefaultIfEmpty()
             join c3 in _context.Cicmpy on ork.Verzdebnr equals c3.Debnr into c3j
             from c3 in c3j.DefaultIfEmpty()
             select new { ors, ork, c1, c2, c3 })
             .FullOuterJoin(_context.Humres, left => left.ork.represent_id, h => h.res_id, (_, left, h) => new { left.ors, left.ork, left.c1, left.c2, left.c3, h });

【讨论】:

  • 感谢您的回答,我必须在所有 Expression 方法中进行哪些更改才能使我的代码正常工作?现在我在“return leftItems.LeftOuterJoin(”上得到一个错误,它说“IQueryable 不包含“LeftOuterJoin”的定义,并且没有可以找到接受 IQueryable 类型的第一个参数的扩展方法”
  • 编辑:nvm 我想我明白了,非常感谢!!标记为答案!
  • 再次编辑:我仍然得到“无法从用法中推断出方法 FullOuterJoin(...) 的类型参数。尝试具体指定类型参数”,我将如何执行此操作?
  • 这表明您提供给函数的参数不匹配,因此编译器无法推断出正确的类型。
  • 是的,但是我复制并粘贴了您所写的内容,在您的 FullOuterJoin(...) 等中,但它不起作用?在您的方法中,您需要 FullOuterJoin(tableLeft, tableRight, TableKey (哪一个?), TResult),您写的 FullOuterJoin(_context.Humres, left => etc... 是错误的还是?
猜你喜欢
  • 1970-01-01
  • 2012-03-29
  • 1970-01-01
  • 2015-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-17
  • 1970-01-01
相关资源
最近更新 更多