在多对多关系中进行复杂搜索的最快SQL表达式?

在多对多关系中进行复杂搜索的最快SQL表达式?,sql,performance,many-to-many,Sql,Performance,Many To Many,在产品标签表中,列为 标识、产品标识、标签标识 如果我想搜索tag1、tag2或tag3产品,直接方法是: SELECT DISTINCT productId FROM product_tags WHERE tagId IN (2,4); SELECT productId FROM product_tag WHERE tag_id IN (tag1, tag2, tag3) GROUP BY productId HAVING COUNT(*) = 3 如果我想搜索tag1、tag2和tag3

在产品标签表中,列为

标识、产品标识、标签标识

如果我想搜索tag1、tag2或tag3产品,直接方法是:

SELECT DISTINCT productId FROM product_tags WHERE tagId IN (2,4);
SELECT productId FROM product_tag WHERE tag_id IN (tag1, tag2, tag3) GROUP BY productId HAVING COUNT(*) = 3
如果我想搜索tag1、tag2和tag3产品,直接方法是:

SELECT DISTINCT productId FROM product_tags WHERE tagId IN (2,4);
SELECT productId FROM product_tag WHERE tag_id IN (tag1, tag2, tag3) GROUP BY productId HAVING COUNT(*) = 3
但问题是,我是否希望搜索具有复杂标签关系的产品,例如:

属于tag1、tag2或tag3的产品 和标签4或标签5或标签6和标签 7或tag8或tag9

性能最好的SQL表达式是什么?最好是优雅的

编辑:
最重要的性能提升是添加索引,正如Remus在评论中建议的那样。

使用基于集合的语言(如SQL)确实无法直接做到这一点

除非没有productId、tagId的副本,否则您的简单和版本也无法工作

对于复杂的关系,有必要将查询分解为几个子查询。所有条款的第一次中断:

WHERE tag_id IN (tag1, tag2, tag3)
WHERE tag_id IN (tag4, tag5, tag6)
WHERE tag_id IN (tag7, tag8, tag9)
然后对查询结果进行交集

如果这些子查询中的任何一个不是简单的OR列表,而是在更复杂的逻辑结构中包含AND,则需要递归地进一步分解这些子查询

换句话说,您可以沿着AND子句递归地分解逻辑树,然后在每个树级别对查询结果进行交集

这样做可能要比生成一个巨大的SQL来一次性返回结果快得多,因为每个简单的OR'ed列表都可以利用您在标记id上的索引。

我注意到

怎么样

SELECT DISTINCT t1.productId FROM product_tags t1
JOIN product_tags t2 ON t1.productId=t2.productId AND t2.tagId IN (tag4,tag5,tag6)
JOIN product_tags t3 ON t1.productId=t3.productId AND t3.tagId IN (tag7, tag8, tag9)
AND t1.tagId IN (tag1,tag2,tag3)

如果能以某种方式去除DISTINCT,效果会更好。

将所有3组联合起来。它们是3个选择项,但它们非常简单。

性能不会太好,但您可以执行嵌套查询

SELECT 
ProductID FROM
Products 
WHERE tag_id IN (tag1, tag2, tag3)
AND ProductID IN (
SELECT 
ProductID FROM
Products 
WHERE tag_id IN (tag4, tag5, tag6)
)
AND ProductID IN (
SELECT 
ProductID FROM
Products 
WHERE tag_id IN (tag7, tag8, tag9)
)

是否提前知道标签的数量?如果它不是随时间增长的东西,我会将tag_id更改为位集

WITH T AS 
 (SELECT product_id, bit_or((1<<tag_id)::bigint) tagset 
  FROM product_tag GROUP BY product_id) 
SELECT product_id 
WHERE (tagset & 7)>0 AND (tagset & 56)>0 AND (tagset & 448)>0;
我在这里使用了Postgres,其中&被称为按位和;如果product_标记表中不允许重复,那么bit_or是聚合函数SUM,在这里也同样适用。面具上的魔法数字只是二的幂的一点点。双结肠是一种后结肠铸型。这里的所有东西都可以在其他地方使用稍微不同的名称。但是PG也有不确定大小的位字符串,并且可以为大量标记实现与位字符串相同的逻辑

顺便说一下,匹配所有标记的情况就是tagset&mask=mask


这就是为什么你的指数运行得如此之快;它们可能被合并到这种类型的测试中。

我不同意使用交叉点,如果我没有弄错的话,这将是非常慢的。当引擎被迫在每一行上评估复杂的逻辑时,不会比完整的表扫描慢。如果每个子查询只选择一个非常小的行子集,并且可以通过索引快速提取这些行,则相交速度会快得多。性能不是来自SQL文本,而是来自索引。我应该如何对此进行索引?@Remus,我在product\u tag product\u id ASC上使用了CREATE index main.product\u category\u fastindex,tag_id ASC并看到性能提高了10倍以上,非常感谢!!您还应该尝试添加第二个索引,其标签id位于最左边的列:CREATE index main.product\u tag\u fastindex ON products\u tag\u id ASC,product\u id ASC。通过这种方式,优化器可以在两个索引之间进行选择,您可能会看到更快的结果。@Remus,这是一个很好的建议。对于某些标记分布,例如许多类型的不同标记,每个标记的行数较少,通过标记索引并仅获取公共行可能会更快。如果您只考虑一个级别的and,这可能会起作用。查询优化器基本上应该将其转换为三个子查询,然后再进一步处理,否则它将在跟踪主键时运行得慢得多。这是我最后使用的版本,使用索引时速度很快。SQL也很容易生成,然后你在一个数据库引擎上运行,这个引擎有一个非常好的查询优化器,它特别考虑主键上的自连接,并优化了多行访问。在许多引擎(尤其是嵌入式引擎)上,连接速度非常慢,因此您总是希望限制连接的数量并尽可能多地限制连接空间。标记的数量未知,而且应该很多。这和把一列变成布尔值不一样吗?类似,基本上是布尔向量。再想一想,我想我会对AND使用连接,对一组where子句使用OR的表进行AND。DB可以针对这种类型的查询进行优化,或者沿着创建自己的位集的路线进行查询。诀窍是在WHERE中用SELECT X FROM Y AS Y1替换和,并在Y1上将Y连接为Y2。product_id=Y2。product_id和Y2。tag_id=某物,其中Y1。tag_id=某物。。。而且 具有析取条件的。还在思考;有趣的问题。