【问题标题】:Writing set based SQL query without set operations (EXCEPT, INTERSECT)在没有集合操作的情况下编写基于集合的 SQL 查询(EXCEPT、INTERSECT)
【发布时间】:2011-02-10 12:58:28
【问题描述】:

我有三个表:UserObjectsUserObjectsRelationsUserClasses UserObjects 和 UserClasses 之间的模型 M:N 关系。

现在我需要选择这个:

All(UserObjects) - Intersect(UserObjectRelations -> UserObjects).Where(UserObjectRelation -> UserClassId IN (some list))

这意味着我有一个 UserClassIds 列表,用于过滤 UserClasses(或直接关系),我需要找到所有未分配给 所有这些 UserClasses 的 UserObjects。

示例:假设我有 UserClassId IN (1,2) 过滤的 UserObjectRelations:

UserClassId |  UserObjectId 
--------------------------
     1      |        1
     2      |        1
     2      |        2

我还有许多其他用户对象。我的查询结果应该是此结果集中未提及的所有 UserObjects + Id = 2 的 UserObject,因为它与所有请求的 UserClasses 无关。

问题是 SQL 查询是由实体框架生成的(我们无法完全控制生成的 SQL),所以我们最初使用 INTERSECT 的方法失败了 - 对于许多 UserClasses,它会创建过于复杂的查询,有时 SQL Server 会引发错误因为嵌套很深。

它创建这样的查询(但非常庞大,因为 EF 不使用 * 表示法并且它真的很喜欢很多嵌套的 SELECT):

SELECT Unsused.* 
FROM dbo.UserObjects AS Unsused
WHERE Unsused.IsDeleted = 0
EXCEPT (
    SELECT U.* 
    FROM dbo.UserObjects AS U
    INNER JOIN dbo.UserObjectRelations AS UR ON UR.UserObjectId = U.Id
    WHERE UR.UserClassId = 1
    INTERSECT (
        SELECT U.* 
        FROM dbo.UserObjects AS U
        INNER JOIN dbo.UserObjectRelations AS UR ON UR.UserObjectId = U.Id
        WHERE UR.UserClassId = 2    
    ))

我现在正在重写查询 - 首先在 SQL 中,稍后我将尝试在 Linq-To-Entities 中定义它。我想出了这个:

SELECT Unused.*
FROM dbo.UserObjects AS Unused
LEFT JOIN (
    SELECT UsageReport.Id
    FROM (
        SELECT Tmp.Id, COUNT(*) AS Usage
        FROM dbo.UserObjects AS Tmp 
        INNER JOIN dbo.UserObjectRelations AS DefiningRelations ON
            Tmp.Id = DefiningRelations.UserObjectId
        WHERE DefiningRelations.UserClassId IN (1, 2)
        GROUP BY Tmp.Id) AS UsageReport
    WHERE UsageReport.Usage = 2
) AS Used ON Used.Id = Unused.Id
WHERE Unused.IsDeleted = 0 AND Used.Id IS NULL

查询可能看起来不太好,但我已经在尝试避免我不知道如何转换为 Linq-To-Entities 的构造。

我仍然对查询不满意。我不喜欢这部分:WHERE UsageReport.Usage = 2 将内部选择过滤为仅由两个用户类使用的用户对象。此参数必须是动态的,并且始终表示传入 IN 子句的 Id 数量。

你知道如何更好地编写这样的查询吗?

【问题讨论】:

  • 在您的查询中,您根本没有使用您的LEFT JOIN,我认为您缺少过滤器。也许Used.Id IS NULL
  • @Lamak:你说得对,我已经修改了我的查询。谢谢。

标签: sql sql-server tsql entity-framework linq-to-entities


【解决方案1】:

另一个也使用 COUNT() 的:

SELECT u.*
FROM UserObjects
  LEFT JOIN (
    SELECT UserObjectId
    FROM UserObjectRelations
    WHERE UserClassId IN (1, 2)
    GROUP BY UserObjectId
    HAVING COUNT(DISTINCT UserClassId) = 2
  ) r ON u.Id = r.UserObjectId
WHERE r.UserObjectId IS NULL
  AND u.IsDeleted = 0

我在这里使用 COUNT(DISTINCT),但如果确定那里不可能重复,那么 COUNT(*) 可能会更好。

但是,如果您真的强烈反对这样使用 COUNT,我建议您重新考虑 INTERSECT 方法,而不是您展示的方式。

我会这样使用它:

SELECT u.*
FROM UserObjects
  LEFT JOIN (
    SELECT UserObjectId FROM UserObjectRelations WHERE UserClassId = 1
    INTERSECT
    SELECT UserObjectId FROM UserObjectRelations WHERE UserClassId = 2
  ) r ON u.Id = r.UserObjectId
WHERE r.UserObjectId IS NULL
  AND u.IsDeleted = 0

如您所见,这里没有 COUNT,而且看起来不太重。我相信您可以通过这种方式包含许多类,并且您不需要在那里使用括号。

【讨论】:

  • 我喜欢第二种方法。不幸的是,当我将它翻译成 Linq-To-Entities 时,它会产生糟糕的 SQL 查询。我使用了我描述的解决方案。
  • 谢谢,拉迪斯拉夫!这完全出乎意料,非常好!
【解决方案2】:

这行得通吗?不过,它仍然使用列表的计数。我不确定在没有存储过程的情况下是否有解决这个问题的好方法......

SELECT o.* FROM UserObjects o
           LEFT JOIN UserObjectsRelations r ON o.id = r.UserObjectId
WHERE r.UserClassId IN (1,2) OR r.UserClassId IS NULL
GROUP BY o.id HAVING COUNT(o.id) < 2

更新:抱歉,之前没有正确考虑。不确定这是否是最好的方法,但你确实摆脱了 IN 子句中的 Id 数量(我用 MySQL 做到了,如果它在 TSQL 中不符合 kosher 则很抱歉)。这是我想出的:

SELECT o.* FROM UserObjects o, 
                (SELECT o.id oid, c.id cid FROM UserObjects o, UserClasses c
                 WHERE c.id IN (1,2)
                ) sub
           LEFT JOIN UserObjectsRelations r ON sub.oid = r.UserObjectId AND
                                               sub.cid = r.UserClassId
WHERE o.id = sub.oid AND r.UserClassId IS NULL
GROUP BY o.id

【讨论】:

  • AFAIK,在 SQL Server 中,所有选择的非聚合列都应该包含在 GROUP BY 子句中,否则在运行脚本时会收到错误消息。
  • 是的,它有效。那个子部分 - 不是交叉连接吗?我用 DISTINCT 替换了 GROUP BY。现在我必须考虑将其重写为 Linq 查询 :(
  • 最后我使用了我描述的解决方案。您的解决方案还不错,但查询的性能更差。
  • @Ladislav,它的性能会很差,因为 sub 是交叉连接。您可以通过基于SELECT sub.oid UserObjectId, sub.cid UserClassId FROM (SELECT o.id oid, c.id cid FROM UserObjects o, UserClasses c) sub LEFT JOIN UserObjectsRelations r ON sub.oid = r.UserObjectId AND sub.cid = r.UserClassId WHERE r.UserClassId IS NULL 创建一个视图(可能是 NotUserObjectsRelations,因为它将包含当前不在其中的 UserObjectsRelations 中所有可能的条目)来改进它。不过,我不是 DBA,也不了解您的情况,所以我不能肯定这是否是个好主意。
猜你喜欢
  • 2016-06-28
  • 2021-01-21
  • 1970-01-01
  • 1970-01-01
  • 2021-10-05
  • 1970-01-01
  • 2019-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多