在我的数据库中,我有一些项目,这些项目反映了用户填写的文档的属性。文档中给出的每个值,例如您为某个字段选择某个选项或选中一个复选框,就成为我表格中的一项/属性。
此类属性可能是:吸烟者,不吸烟者,地区(欧洲,美国,...),头发颜色
在表中,这大致如下:
Document
ID | Name
1 | doc-1
2 | doc-2
3 | doc-3
Attribute
ID | Name
1 | Smoker
2 | Non-Smoker
3 | Region-Europe
4 | Region-USA
5 | Hair-Brown
6 | Hair-Blond
Item
ID | Document | Attribute
1 | 1 | 1
2 | 1 | 4
3 | 2 | 2
4 | 2 | 3
5 | 2 | 5
6 | 3 | 2
7 | 3 | 6
为提供搜索可能性,应允许用户建立通用查询。例如,我要查找具有以下属性的文档:
(Smoker AND Region-USA) OR (Non-Smoker AND Region-Europe AND Hair-Blond)
(将导致找到文件#1)
如何以最有效的方式执行此类查询,并可能使用EF-core和linq-to-sql将其下推到SQL? 我实际上如何以最有效的方式在计划SQL中进行查询?
我可以很容易地在内存中完成此操作,但是由于我的数据库包含超过10万个项目,因此很快就会变慢。
谢谢您的帮助!
更新:关于SO的相关问题
答案 0 :(得分:0)
更多研究向我展示了我已经期望的结果:可以使用使用SQL IN语句的解决方案,并且该解决方案实际上正在很好地将查询工作分派给服务器,但是对于大量标签而言可能效率不高。
幸运的是,用户不会定期执行非常复杂的查询,并且会在复杂查询上接受一点等待时间,因此我可以忽略这一点。
要链接这些语句的来源:
现在大致绘制最终解决方案,下面是一些代码:
通过使用IN语句,在子查询中,我可以过滤所有应用了特定属性的文档。通过使用AND / OR组合这些IN语句,我可以构建所需的表达式。
SELECT i.Document
FROM Item i INNER JOIN Attribute a on i.Attribute = a.ID
WHERE
i.Document IN (
SELECT ii.Document
FROM Item ii INNER JOIN Attribute ai on ii.Attribute = ai.ID
WHERE ai.Name = "Smoker"
)
AND
i.Document IN (
SELECT ii.Document
FROM Item ii INNER JOIN Attribute ai on ii.Attribute = ai.ID
WHERE ai.Name = "Region-USA"
)
OR
i.Document IN (
SELECT ii.Document
FROM Item ii INNER JOIN Attribute ai on ii.Attribute = ai.ID
WHERE ai.Name = "Non-Smoker"
)
AND
i.Document IN (
SELECT ii.Document
FROM Item ii INNER JOIN Attribute ai on ii.Attribute = ai.ID
WHERE ai.Name = "Region-Europe"
)
AND
i.Document IN (
SELECT ii.Document
FROM Item ii INNER JOIN Attribute ai on ii.Attribute = ai.ID
WHERE ai.Name = "Hair-Blond"
)
性能提升
要限制子查询中所需的JOIN数量,可以先选择所需属性的ID。
SELECT ID, Name FROM Attribute WHERE Name in ('Smoker', 'Non-Smoker', ...)
使用这些ID,子查询看起来会容易得多,因为我们可以跳过JOIN:
SELECT i.Document
FROM Item i INNER JOIN Attribute a on i.Attribute = a.ID
WHERE
i.Document IN (SELECT ii.Document FROM Item ii WHERE ii.Attribute = 1) -- Smoker
AND
i.Document IN (SELECT ii.Document FROM Item ii WHERE ii.Attribute = 4) -- Region-USA
OR
...
更新
两种方法的测量时间
我确实执行了与上述查询类似的查询:在SQL Server上执行(1 AND 2)或(3 AND 4 AND 4),并具有一组合理大小的文档(130),项目(4122)和属性( 〜400)。 在我的机器上可以测量以下时间:
答案 1 :(得分:0)
这是LINQ扩展类,可帮助构建查询。我不去解析表达式并构建正确的查询作为练习:)。
首先,这是我们要构建的基础:
public class DocItemJoin {
public Documents d { get; set; }
public IEnumerable<int> ig { get; set; }
}
var DocItems = Document.GroupJoin(Item, d => d.ID, i => i.Document, (d, ig) => new DocItemJoin { d = d, ig = ig.Select(i => i.Attribute) });
// (Smoker AND Region-USA) OR (Non-Smoker AND Region-Europe AND Hair-Blond)
var ans = DocItems.Where(dig => (dig.ig.Contains(1) && dig.ig.Contains(4)) || (dig.ig.Contains(2) && dig.ig.Contains(3) && dig.ig.Contains(6)))
.Select(dig => dig.d);
使用DocItems
作为基础,我们可以使用Contains
查询每个属性。
使用扩展库,我们可以动态地构建相同的查询:
var whereLeft = 1.HasAttrib().qAnd(4.HasAttrib());
var whereRight = 2.HasAttrib().qAnd(3.HasAttrib()).qAnd(6.HasAttrib());
var whereBody = whereLeft.qOr(whereRight);
var ans = DocItems.Query(whereBody);
最后,这是构建Expression
树的扩展类:
public static class QueryBuilder {
private static MethodInfo containsMethod = typeof(Enumerable).GetMethods().Single(mi => mi.Name == "Contains" && mi.GetParameters().Length == 2).MakeGenericMethod(typeof(int));
public static MethodCallExpression qContains(this Expression p, int attrib) => Expression.Call(containsMethod, p, Expression.Constant(attrib));
public static BinaryExpression qAnd(this Expression l, Expression r) => Expression.AndAlso(l, r);
public static BinaryExpression qOr(this Expression l, Expression r) => Expression.OrElse(l, r);
static ParameterExpression digParm = Expression.Parameter(typeof(DocItemJoin), "dig");
static MemberExpression digParmig = Expression.Property(digParm, "ig");
public static MethodCallExpression HasAttrib(this int attrib) => digParmig.qContains(attrib);
static Expression<Func<DocItemJoin, Documents>> selectLambda = Expression.Lambda<Func<DocItemJoin, Documents>>(Expression.Property(digParm, "d"), digParm);
public static IQueryable<Documents> Query(this IQueryable<DocItemJoin> src, Expression whereBody)
=> src.Where(Expression.Lambda<Func<DocItemJoin, bool>>(whereBody, digParm)).Select(selectLambda);
}