缩小多对多关系中可能的组合
Narrowing down possible combinations in a many-to-many relationship
假设
我有以下 3 个数据库表:
Foobar:
- id
- 名字
标签:
- id
- 名字
Foobar_Tags:
- id
- foobar_id
- tag_id
Foobar 的数量很多,它们被随机标记了一个或多个标签。
问题
我收到了标签列表 - 例如('tag1', 'tag2', 'tag3')
现在我想要获取与 foobar 相关联的标签列表,其中 foobar 也与接收到的标签列表相关联。
为了更形象化:
- foobar_1 有标签 'tag1'、'tag2'
- foobar_2 有标签 'tag2'、'tag3'
- 请求的标签:'tag2'
- 结果:'tag1'、'tag3'
- 请求的标签:'tag1'
- 结果:'tag2'
- 请求的标签:'tag3'
- 结果:'tag2'
- 请求的标签:'tag1'、'tag2'
- 结果:None
当前方法
我正在使用 Django,我当前的方法如下所示(标签的 foobar 是一个简单的 m2m 字段):
if tag_list:
available_tags = Tag.objects
for tag in tag_list:
available_tags = available_tags.filter(foobar__tags__tag=tag).exclude(tag=tag)
available_tags = available_tags.distinct()
else:
available_tags = Tag.objects.all()
available_tags = available_tags.annotate(num_foobars=Count('foobar', distinct=True)) \
.order_by('-num_foobars') \
.exclude(num_foobars=0)
我得到了我想要的结果,但我不确定我在这里使用的方法是否正确。结果 SQL 仅过滤 2 个标签时已经包含 8 个 INNER JOINS,并且每个添加的标签都会极大地增长,使其非常慢。
示例SQL
这是查找时生成的SQL('tag1','tag2')
SELECT DISTINCT
"tag"."id",
"tag"."name",
COUNT(DISTINCT "foobar_tags"."foobar_id") AS "num_foobars"
FROM "tag"
INNER JOIN "foobar_tags" ON ( "tag"."id" = "foobar_tags"."tag_id" )
INNER JOIN "foobar" ON ( "foobar_tags"."foobar_id" = "foobar"."id" )
INNER JOIN "foobar_tags" T4 ON ( "foobar"."id" = T4."foobar_id" )
INNER JOIN "tag" T5 ON ( T4."tag_id" = T5."id" )
INNER JOIN "foobar_tags" T6 ON ( "tag"."id" = T6."tag_id" )
INNER JOIN "foobar" T7 ON ( T6."foobar_id" = T7."id" )
INNER JOIN "foobar_tags" T8 ON ( T7."id" = T8."foobar_id" )
INNER JOIN "tag" T9 ON ( T8."tag_id" = T9."id" )
WHERE (T5."name" = 'tag1'
AND NOT ("tag"."name" = 'tag1')
AND T9."name" = 'tag2'
AND NOT ("tag"."name" = 'tag2'))
GROUP BY "tag"."id", "tag"."name"
HAVING NOT (COUNT(DISTINCT "foobar_tags"."foobar_id") = 0)
ORDER BY "num_foobars" DESC
问题
- 能否优化查询(使用 Django ORM 或原始 SQL)?
- 是否有此问题的名称(以供进一步搜索)?
不需要为每个额外的标签都加入一个。假设查询包含 tag1
和 tag2
,这是它的 sql:
select distinct tags.id, tags.name from tags inner join foobar_tags
on tags.id = foobar_tags.tagId
where fooId in
(select fooId from tags t inner join foobar_tags ft on t.id = ft.tagId
where
(select count(distinct name) from foobar_tags inner join tags
on tags.id = foobar_tags.tagId
where fooId = ft.fooId and tags.name in('tag2','tag1')--tags query
) = 2 --number of tags in the query
)
AND
name not in ('tag2','tag1')--tags query
我们通过计算属于我们查询的关联标签来找到所有具有所有标签的 foo
s tags.This 计数应该等于查询标签的数量。然后我们 return 个匹配的 foo
个标签,除了属于查询标签的标签。
您可以为任意数量的标签生成此查询,连接数将保持不变。
这是一个fiddle。
假设
我有以下 3 个数据库表:
Foobar:
- id
- 名字
标签:
- id
- 名字
Foobar_Tags:
- id
- foobar_id
- tag_id
Foobar 的数量很多,它们被随机标记了一个或多个标签。
问题
我收到了标签列表 - 例如('tag1', 'tag2', 'tag3')
现在我想要获取与 foobar 相关联的标签列表,其中 foobar 也与接收到的标签列表相关联。
为了更形象化:
- foobar_1 有标签 'tag1'、'tag2'
- foobar_2 有标签 'tag2'、'tag3'
- 请求的标签:'tag2'
- 结果:'tag1'、'tag3'
- 请求的标签:'tag1'
- 结果:'tag2'
- 请求的标签:'tag3'
- 结果:'tag2'
- 请求的标签:'tag1'、'tag2'
- 结果:None
当前方法
我正在使用 Django,我当前的方法如下所示(标签的 foobar 是一个简单的 m2m 字段):
if tag_list:
available_tags = Tag.objects
for tag in tag_list:
available_tags = available_tags.filter(foobar__tags__tag=tag).exclude(tag=tag)
available_tags = available_tags.distinct()
else:
available_tags = Tag.objects.all()
available_tags = available_tags.annotate(num_foobars=Count('foobar', distinct=True)) \
.order_by('-num_foobars') \
.exclude(num_foobars=0)
我得到了我想要的结果,但我不确定我在这里使用的方法是否正确。结果 SQL 仅过滤 2 个标签时已经包含 8 个 INNER JOINS,并且每个添加的标签都会极大地增长,使其非常慢。
示例SQL
这是查找时生成的SQL('tag1','tag2')
SELECT DISTINCT
"tag"."id",
"tag"."name",
COUNT(DISTINCT "foobar_tags"."foobar_id") AS "num_foobars"
FROM "tag"
INNER JOIN "foobar_tags" ON ( "tag"."id" = "foobar_tags"."tag_id" )
INNER JOIN "foobar" ON ( "foobar_tags"."foobar_id" = "foobar"."id" )
INNER JOIN "foobar_tags" T4 ON ( "foobar"."id" = T4."foobar_id" )
INNER JOIN "tag" T5 ON ( T4."tag_id" = T5."id" )
INNER JOIN "foobar_tags" T6 ON ( "tag"."id" = T6."tag_id" )
INNER JOIN "foobar" T7 ON ( T6."foobar_id" = T7."id" )
INNER JOIN "foobar_tags" T8 ON ( T7."id" = T8."foobar_id" )
INNER JOIN "tag" T9 ON ( T8."tag_id" = T9."id" )
WHERE (T5."name" = 'tag1'
AND NOT ("tag"."name" = 'tag1')
AND T9."name" = 'tag2'
AND NOT ("tag"."name" = 'tag2'))
GROUP BY "tag"."id", "tag"."name"
HAVING NOT (COUNT(DISTINCT "foobar_tags"."foobar_id") = 0)
ORDER BY "num_foobars" DESC
问题
- 能否优化查询(使用 Django ORM 或原始 SQL)?
- 是否有此问题的名称(以供进一步搜索)?
不需要为每个额外的标签都加入一个。假设查询包含 tag1
和 tag2
,这是它的 sql:
select distinct tags.id, tags.name from tags inner join foobar_tags
on tags.id = foobar_tags.tagId
where fooId in
(select fooId from tags t inner join foobar_tags ft on t.id = ft.tagId
where
(select count(distinct name) from foobar_tags inner join tags
on tags.id = foobar_tags.tagId
where fooId = ft.fooId and tags.name in('tag2','tag1')--tags query
) = 2 --number of tags in the query
)
AND
name not in ('tag2','tag1')--tags query
我们通过计算属于我们查询的关联标签来找到所有具有所有标签的 foo
s tags.This 计数应该等于查询标签的数量。然后我们 return 个匹配的 foo
个标签,除了属于查询标签的标签。
您可以为任意数量的标签生成此查询,连接数将保持不变。
这是一个fiddle。