我的数据库中存在多对多关系(但我的问题也可能是一对多,因此标题),它将文件与标签链接起来。用户可以使用标签作为过滤器来搜索文件。
例如:
+ (a AND b) - c
表示我想要所有标记有标记a
和标记b
的文件,而不是标记为c
的文件。
在伪代码中,这将是:
getFiles(filter) {
let matching_files = [];
for file in files {
let tags = file.tags;
if filter.match(tags) { // something non-trivial
matching_files.push(file);
}
}
matching_files
}
我刚刚意识到我想要做的是基于搜索而不是基于一些行,而是基于一组行,这不是SQL的意思。
我已经知道如何实现这一点,这将通过生成基于搜索查询的动态交叉/除外。所以先前的查询将是:
(SELECT file_id from files, tags WHERE tags.id = a) INTERSECT
(SELECT file_id from files, tags WHERE tags.id = b) /* <- this handles + (a AND b) */ EXCEPT
(SELECT file_id from files, tags WHERE tags.id = c) -- this one handles the - c
所以+
群组是一堆联盟,AND
个关键字会生成INTERSECT
s,OR
可能只是OR
WHERE
1}}组中的子句,-
对于之前已过滤的所有内容都是EXCEPT
。
所以我的问题是,有没有更简单的方法来做所有这些?因为首先这看起来非常hacky,然后当文件数量增长时,它看起来并不高效 ,因为我将查询每个组的所有文件。解决方案不一定是SQL(如果有其他类型的数据存储可以更好地处理这个问题),但请注意我正在为桌面应用程序执行此操作(所以使用我可以嵌入我的程序会很好)。谢谢!
答案 0 :(得分:0)
如果您想使用intersect
,可以执行以下操作:
select file_id
from file_tags
where file_tags.tag_id = a
intersect
select file_id
from file_tags
where file_tags.tag_id = b
intersect
select file_id
from file_tags
where file_tags.tag_id = c;
我通常会使用group by
和having
执行此操作:
select file_id
from file_tags
where tag_id in (a, b, c)
having count(*) = 3;
答案 1 :(得分:0)
首先,您在JOIN
等查询中错过tags.file_id = files.file_id
条件。您可以使用EXISTS
和NOT EXISTS
执行相同的工作。
SELECT file_id
FROM files
WHERE EXISTS (
SELECT 1 FROM tags
WHERE tags.file_id = files.file_id and tags.id = a
) and EXISTS (
SELECT 1 FROM tags
WHERE tags.file_id = files.file_id and tags.id = b
) and NOT EXISTS (
SELECT 1 FROM tags
WHERE tags.file_id = files.file_id and tags.id = a
)
如果你有一个类似+(a OR b)的过滤器 - c则可以使用以下sql表示
SELECT file_id
FROM files
WHERE EXISTS (
SELECT 1 FROM tags
WHERE tags.file_id = files.file_id and tags.id IN (a, b)
) and NOT EXISTS (
SELECT 1 FROM tags
WHERE tags.file_id = files.file_id and tags.id = a
)
效率主要与适当的指标有关。如果你创建了诸如tags(id, file_id)
之类的索引,那么它应该没问题(因为你可能已经在file(file_id)
上有了一个)。