这是“set-within-sets”查询的示例。最通用的方法是使用group by 并将逻辑放在having 子句中。对于您的情况:
SELECT B.name, B.author, B.id, B.finished, B.manual
FROM books B INNER JOIN
xrefbookstags XRBT
ON XRBT.idBooks = B.id JOIN
tags T
ON XRBT.idTags = T.id
group by B.name, B.author, B.id, B.finished, B.manual
having sum(case when t.name = 'novel' then 1 else 0 end) > 0 and
sum(case when t.name = 'biography' then 1 else 0 end) > 0 and
sum(case when t.name not in ('novel', 'biography') then 1 else 0 end) = 0;
逻辑如下。当至少一个标签是'novel' 时,第一个子句为真。当至少有一个子句为'biography' 时,第二个子句为真,当没有其他标签时,第三个子句为真。
这很容易概括。如果您想要具有这两个标签但可以有其他标签的书籍,只需省略第三个子句:
having sum(case when t.name = 'novel' then 1 else 0 end) > 0 and
sum(case when t.name = 'biography' then 1 else 0 end) > 0;
如果您想要有其中之一的书籍:
having sum(case when t.name = 'novel' then 1 else 0 end) > 0 or
sum(case when t.name = 'biography' then 1 else 0 end) > 0;
如果您想要有这两个加“历史”的书籍,那么您只需将其添加到:
having sum(case when t.name = 'novel' then 1 else 0 end) > 0 and
sum(case when t.name = 'biography' then 1 else 0 end) > 0 and
sum(case when t.name = 'historical' then 1 else 0 end) > 0;
而且,如果你想要,但不想做饭:
having sum(case when t.name = 'novel' then 1 else 0 end) > 0 and
sum(case when t.name = 'biography' then 1 else 0 end) > 0 and
sum(case when t.name = 'historical' then 1 else 0 end) > 0 and
sum(case when t.name = 'cooking' then 1 else 0 end) = 0;
编辑:
如果您有一个逗号分隔的要匹配的标签列表,那么您可以这样做:
having sum(case when ','+@List+',' not like '%,'+t.name+',%' then 1 else 0 end) = 0 and
count(distinct t.name) = 1 + len(@list) - len(replace(@list, ',', ''))
第一个子句表示所有标签都在列表中。第二个说标签的长度就是列表的长度。
这本质上是伪代码。不同的数据库对len() 函数有不同的名称,将字符串连接在一起的方式不同,表示变量值的方式也不同。但意图应该很清楚。