基于SQL集的编写集,无需设置操作(EXCEPT,INTERSECT)

时间:2011-02-10 12:58:29

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

我有三个表:UserObjectsUserObjectsRelationsUserClasses,用于模拟UserObjects和UserClasses之间的M:N关系。

现在我需要选择这个:

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

这意味着我有UserClassId列表,我用它来过滤UserClasses(或直接关系),我需要找到所有未分配给所有那些UserClasses的UserObject。

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

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

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

问题是SQL查询是由Entity Framework生成的(我们没有完全控制生成的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数。

您是否知道如何以更好的方式编写此类查询?

2 个答案:

答案 0 :(得分:1)

这有用吗?但它仍然使用列表的计数。如果没有存储过程,我不确定是否有好方法......

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中的犹太人)。这就是我想出的:

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

答案 1 :(得分: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,而且它看起来不太重。我相信你可以用这种方式包含很多课程,而且你不需要在那里使用括号。