1

我有以下表格:

  • 书籍:书籍清单
  • tags : 标签列表(小说、传记等)
  • xrefbookstags :交叉引用表(一本书有多个标签)

我想做的是给定一组标签,找到标签与该组完全匹配的书。这是我尝试过的:

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
    WHERE T.name IN ('novel','biography')

这给了我一个包含而不是集合的平等。所以我想我应该计算集合中的标签数量并匹配集合的大小。

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
    WHERE T.name IN ('novel','biography') AND count(T.id)=2

但这失败了。所以我的问题来了:我可以在这个交叉引用上下文中匹配集合吗?

4

3 回答 3

2

你想要那些书

a)该书没有缺少所需的标签,并且 b)该书没有不在所需标签列表中的标签。

将这些标签名称的 id 放入一个表中(这里是动态完成的)并执行此操作。(没有 SQL Fiddle 或 CREATE TABLE/INSERT 示例数据,所以我没有运行它来检查每个细节)

WITH TagIDs(id) AS ( --required tag ids
  SELECT id
  FROM tags
  WHERE name in ('novel', 'biography')
)

  SELECT B.name, B.author, B.id, B.finished, B.manual --select those books
  FROM books B

  WHERE --a)
  NOT EXISTS (  -- there is no
    SELECT * FROM TagIDs --  required tag
    WHERE TagIDs.id NOT IN ( -- that's missing
      SELECT XRBT.idTags FROM xrefbookstags XRBT -- from the list of tags
      WHERE XRBT.idBooks=B.id -- for this particular book
    )
  )
  AND --b)
  NOT EXISTS ( -- there is no
    SELECT * FROM xrefbookstags XRBT --tag
    WHERE XRBT.idBooks=B.id --for this particular book
    AND XRBT.idTags NOT IN ( --that's missing
      SELECT id FROM TagIDs --from the list of required tags
    )
  )
于 2013-08-11T22:42:16.833 回答
1

你可以结合EXISTSNOT EXISTS

SELECT B.name, B.author, B.id, B.finished, B.manual
FROM books B
WHERE EXISTS(
    SELECT 1 FROM xrefbookstags XRBT 
    INNER JOIN Tags T ON XRBT.idTags=T.id
    WHERE XRBT.idBooks=B.id
    AND T.name = 'novel'
)
AND EXISTS(
    SELECT 1 FROM xrefbookstags XRBT 
    INNER JOIN Tags T ON XRBT.idTags=T.id
    WHERE XRBT.idBooks=B.id
    AND T.name = 'biography'
) 
AND NOT EXISTS(
    SELECT 1 FROM xrefbookstags XRBT 
    INNER JOIN Tags T ON XRBT.idTags=T.id
    WHERE XRBT.idBooks=B.id
    AND T.name NOT IN ('novel','biography')
)
于 2013-08-11T21:20:04.827 回答
0

这是“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()函数有不同的名称,将字符串连接在一起的方式不同,表达变量值的方式也不同。但意图应该是明确的。

于 2013-08-11T22:20:57.507 回答