如何使用子查询作为黑名单排除查询中的结果

How to exclude results in a query using a subquery as blacklist

我正在尝试查询数据库中的文本,使用(子)查询作为“黑名单”。扭曲:如果主查询的任何结果与子查询的任何结果具有相同的开头,则应跳过它们。

一些背景知识:我正在构建一个文件浏览器,并使用 table 文件夹名称来维护条目的“收藏”状态。这些收藏旨在以级联方式工作,即“明确”收藏路径下方的任何路径都将“隐式”收藏。

考虑以下数据:

foldername is_favorite is_implicit_favorite
foo/ 1 0
foo/bar/ 1 1
foo/bar/baz/ 0 1
foo/bar2/ 0 1
foo/bar2/baz/ 0 1
foo2/bar/ 0 0
foo2/bar/baz/ 0 0

添加收藏夹很容易:给定路径下的所有文件夹都将设置其“隐式”状态。但我发现很难想出一个简单的相反方法——“取消收藏”一个文件夹。因为在这里,我希望查询跳过应该保持隐式收藏的文件夹(在上面的示例中,取消收藏“foo/”应该跳过“foo/bar”下面的文件夹)。

我尝试了各种解决方案 - 以下方法很接近,但不幸的是只适用于 单个 最喜欢的子文件夹:

SELECT DISTINCT folders.foldername FROM folders 
JOIN (
    SELECT folders.foldername FROM folders 
    WHERE folders.foldername LIKE 'foo/' || '%'
    AND folders.is_favorite = 1
) favs ON folders.foldername NOT LIKE favs.foldername || '%'
WHERE folders.foldername LIKE 'foo/' || '%'

它做我想做的事——不喜欢“foo”应该导致“foo/bar2/”和“foo/bar2/baz/”不再被隐式收藏,而“foo/bar/"(及其子文件夹)保持不变:

folders.foldername
foo/
foo/bar2/
foo/bar2/baz/

在线查看: http://sqlfiddle.com/#!5/8a04e/14/0

编辑:感谢基督徒的回答将我指向 EXCEPT 运算符,我能够想出以下修改后的版本,该版本也适用于多个收藏夹。

SELECT folders.foldername FROM folders 
WHERE folders.foldername LIKE 'foo/' || '%'
EXCEPT 
SELECT folders.foldername FROM folders 
INNER JOIN (
    SELECT folders.foldername FROM folders 
    WHERE folders.foldername <> 'foo/'
    AND folders.is_favorite = 1
) favs 
ON folders.foldername LIKE favs.foldername || '%'   

我很高兴甚至可以用纯 SQL 来表达这一点 - 我曾短暂地想将它变成一个混合 SQL/node.js 解决方案。

当然,我很乐意接受任何可能的优化建议!

谢谢。

如果你有 PostgreSQL 数据库,你可以使用这个:

SELECT folders.foldername FROM folders WHERE folders.foldername LIKE 'foo/%'
EXCEPT ALL
SELECT folders.foldername FROM folders 
WHERE folders.foldername LIKE 'foo/%' AND folders.is_favorite = 1

这可能不是最有效的查询,但它可以解决问题:

SELECT folders.foldername FROM folders 
WHERE folders.foldername LIKE 'foo/' || '%'
EXCEPT 
SELECT folders.foldername FROM folders 
INNER JOIN (
    SELECT folders.foldername FROM folders 
    WHERE folders.foldername <> 'foo/'
    AND folders.is_favorite = 1
) favs 
ON folders.foldername LIKE favs.foldername || '%'   

(更新了我原来的post)