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

问题描述

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

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

考虑以下数据:

文件夹名 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 || '%'   

(更新了我原来的帖子)