我有三个表:UserObjects
,UserObjectsRelations
,UserClasses
,用于模拟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数。
您是否知道如何以更好的方式编写此类查询?
答案 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,而且它看起来不太重。我相信你可以用这种方式包含很多课程,而且你不需要在那里使用括号。