关于 UNION、INTERSECT 和 EXCEPT 的 SqlException

7

有人能帮我解决这个异常吗?我不明白它的含义或如何解决它... 这是一个 SqlException 异常,其消息如下:

使用 UNION、INTERSECT 或 EXCEPT 操作符组合的所有查询必须在其目标列表中具有相等数量的表达式。

当我运行类似于以下伪代码的查询时会出现此异常:

// Some filtering of data
var query = data.Subjects
            .Where(has value)
            .Where(has other value among some set of values);

// More filtering, where I need to have two different options
var a = query
            .Where(some foreign key is null);
var b = query
            .Where(some foreign key is not null)
            .Where(and that foreign key has a property which is what I want);
query = a.Union(b);

// Final filter and then get result as a list
var list = query
            .Where(last requirement)
            .ToList();

如果我删除部分,它可以运行而不会出现异常。所以我知道错误就在那里。但是为什么会出错呢?我该如何修复它?我做的事情太过疯狂了吗?我是否误解了如何使用Union?
基本上我有一些实体,这些实体具有对某些其他实体的外键。我需要获取所有将该外键设置为或者满足某些要求的外键实体。

这是一个 SqlException。我更新了问题 =) - Svish
你确定没有任何查询在投影(select)任何内容吗?我还没有遇到过这种情况,但现在我不记得我所做的联合是使用linq2sql还是针对已检索数据的。 :( - eglasius
关于问题,是的和是的。一切都解决了吗?我离开一会儿了... - Marc Gravell
(那个评论是你的答案吗?) - Svish
无论如何,问题还没有解决,但我需要做更多的编码、思考和尝试你的建议。正在努力解决中,但今天我的大脑运转有点缓慢。 - Svish
显示剩余2条评论
8个回答

10
根据您列出的SQL错误,您可能会遇到与我相同的问题。基本上,当Linq to SQL查询使用Concat或Union扩展方法在两个不同的查询上时,似乎存在一个Linq to SQL中的错误,它单独优化每个投影,而不考虑投影必须保持相同以完成SQL Union的事实。
参考资料: LINQ to SQL使用UNION或CONCAT时生成不正确的TSQL Linq to SQL Union Same Fieldname generating Error 如果这也是您的问题,我已经找到了一个解决方案,如下所示,对我有效。
var queryA = 
    from a in context.TableA
    select new 
    {
        id,
        name,
        onlyInTableA,
    }

var queryB = 
    from b in context.TableB
    let onlyInTableA = default(string)
    select new 
    {
        id,
        name,
        onlyInTableA,
    }

var results = queryA.Union(queryB).ToList();

请注意,即使实际的文字值相同,为了使此技术起作用,您仍需要为每个计算值定义一个新的let语句。到目前为止,这是我找到的唯一合理的解决方案。 - jpierson
2
这里有一些可怕的消息,似乎 .NET Framework 4.0 不仅没有解决这个问题,而且还压制了我上面提出的解决方法。 - jpierson
3
我同意,.NET 4甚至优化了let语句。然而,在v4中停止优化器的一个更加不美观的黑客技巧是对let语句执行一个无用的操作,不改变其结果,比如将空字符串连接或加零。在这种情况下,像以下这样的东西可能会起作用: let onlyInTableA = default(string) + default(string) - Mark Glasgow
@Mark - 感谢你提醒。如果我在升级到.NET 4.0后陷入困境,我会记住这个技巧的。我希望EF最终能够改进这一点。 - jpierson
通过在被投影两次的ID上添加零来解决了我的问题。 - Shaun Rowan

5

由于这似乎是生成的SQL的问题,因此您应该尝试使用SQL Profiler或使用DebuggerWritter类的此代码将SQL写入Visual Studio中的输出窗口。

通常,UNION检索的字段不同于两个查询引起SQL错误。例如,第一个查询可能有3个字段,但第二个查询有4个字段,将会出现此错误。因此,在这种情况下查看生成的SQL肯定会有所帮助。


0

你能否尝试用单个查询语句来实现吗?

.Where(row => row.ForeignKey == null || row.ForeignKey.SomeCondition);

还有一些合并表达式的方法(OrElse),但这并不容易。

不确定错误来自何处!

编辑:尽管我没有测试过,但这应该在逻辑上等同于 UNION:

public static IQueryable<T> WhereAnyOf<T>(
    this IQueryable<T> source,
    params Expression<Func<T, bool>>[] predicates)
{
    if (source == null) throw new ArgumentNullException("source");
    if (predicates == null) throw new ArgumentNullException("predicates");
    if (predicates.Length == 0) return source.Where(row => false);
    if (predicates.Length == 1) return source.Where(predicates[0]);

    var param = Expression.Parameter(typeof(T), "row");
    Expression body = Expression.Invoke(predicates[0], param);
    for (int i = 1; i < predicates.Length; i++)
    {
        body = Expression.OrElse(body,
            Expression.Invoke(predicates[i], param));
    }
    return source.Where(Expression.Lambda<Func<T, bool>>(body, param));
}

问题在于它不仅仅是SomeCondition,还有使用了你之前帮我解决的WhereBetween方法等等。 - Svish
也许你可以将WhereAnyOf(上文提到的)与WhereBetween的主要部分(或WhereBetween的一个版本,该版本返回谓词而不是调用source.Where(predicate))结合使用。 - Marc Gravell
看起来很有前途和有趣...明天第一件事就是试试它!(工作结束了,还要赶公交车!) - Svish
将 OrElse 替换为 AndAlso 会使其成为 WhereAllOf 吗?如果我直接返回 Expression.Lambda 等,而不是在 where 中使用它,是否可以更容易地组合它们?比如我可以做两个 WhereAllOf,然后用 WhereAnyOf 组合它们,再进行 Where 操作? - Svish
为什么你使用 for 而不是 foreach - Svish

0

query = a.Union(b);

改变被捕获的变量不是一个好主意...很可能是错误的原因。

更新:好的并不是

这里有另一个想法。提示在错误信息中。

var a = query
         .Where(some foreign key is null)
         .Select(x => x);

或者通过添加另一个“虚假”的 Where 子句来玩,直到它们变得相等为止 :)


你说得对,重复使用确实会引起混乱,但请注意,其中没有一个被捕获... - Marc Gravell
1
他的意思是,有一个变量query2 = a.Union(b),并且在下游处理中使用query2。然而,我非常怀疑这不是这种情况的问题,因为它实际上没有被捕获。 - Marc Gravell
@svish:你能测试一下Marc说的吗?那就是我想说的。我怀疑它是IQueryable可能会引起问题,只是一种直觉 :) - leppie
我不明白这里应该有什么问题。我知道在foreach循环等方面可能会出现问题,但是在这里我只是更新变量而已?这与执行string s = "test"; s = s.ToUpper();并没有太大的区别。或者我错了吗? - Svish
我不明白。添加一个选择也没有帮助吗? - Svish

0
jpierson 已经正确地概括了问题。
我也遇到了这个问题,这次是由于选择语句中的一些文字导致的:
Dim results = (From t in TestDataContext.Table1 _
Where t.ID = WantedID _
Select t.name, SpecialField = 0, AnotherSpecialField = 0, t.Address).Union _
From t in TestDataContext.Table1 _
Where t.SecondID = WantedSecondID _
Select t.name, SpecialField = 1, AnotherSpecialField = 0, t.Address)

"SpecialField = 0"和"AnotherSpecialField = 0"的第一个子查询被优化了,结果在联合中只使用了一个字段,这显然会失败。
我不得不更改第一个查询,以使SpecialField和AnotherSpecialField具有不同的值,就像第二个子查询一样。


0

我曾经遇到过这个问题。在使用 Sql 08 时,我有两个表函数,在两种情况下都返回 int 和 string。我创建了一个复杂对象,并使用 linq 尝试 UNION。使用 IEqualityComparer 进行比较。所有编译都很好,但崩溃了,出现了不支持的重载。好吧,我意识到讨论的问题似乎涉及到延迟执行。所以我获取集合并放置 ToList(),然后进行 UNION,一切都很好。不确定这是否有帮助,但对我有效。


2
我知道这一点。但你正在做的是在本地执行UNION操作。根据你的情况,这可能是一个好主意,也可能不是。 - Svish

0
我会调用 data.GetCommand(query) 并分析生成的 DbCommand(特别是生成的 SQL 字符串)。这应该能让你知道出了什么问题。
任何地方都没有进行投影,所以我期望两个目标列表是相同的。
你可以尝试将查询缩小到一个仍然无法工作的较小查询。从 query.Union(query) 开始(这至少应该可以工作)。然后逐个添加你的 Where 调用,看看什么时候停止工作。
必须是你的其中一个 Where 调用向你的选择列表添加了额外的列。

已经查看了 SQL,虽然是在调试期间得到的。将其复制并粘贴到新查询中的 SQL 管理器中。但我在那里仍然遇到了相同的错误。问题在于查询非常大,而我对阅读 SQL 不是很稳定... - Svish

0
你是否偶然在变量中传递了一个值到“select”侧,或者返回了同一个字段超过一次? SP1引入了一个错误,它试图“优化”掉这些东西,这可能会导致联合查询失败(因为查询部分会“优化”掉不同的传入参数)。
如果您发布实际查询而不是伪代码,将更容易识别是否存在此问题。
(如果是这种情况,则解决方法是先实现各个部分,然后进行客户端(L2O)联合。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接