仅当一个字段匹配多个值时才匹配(错误?)

时间:2009-10-15 01:51:02

标签: sql mysql

我有精神障碍。我很确定这是一个死的简单的noob问题要解决,但我画的是空白:

我有一个文章标记系统。这是通过具有包含文章ID和标签ID的单独表来完成的,因此可以将多个标签分配给一个文章,反之亦然。这一切都运作良好。但现在我想做的是根据文章是否匹配两个或多个标签,或匹配两个或多个标签但不匹配特定标签,或匹配指定的所有标签等来过滤文章。

--------------------
|ID|ArticleID|TagID|
--------------------
|1 |4000     |123  |
|2 |4000     |3532 |
|3 |4000     |4386 |
|4 |4001     |3532 |
etc...
--------------------

所以它应该返回:

  1. 4000和4001,如果我只搜索3532
  2. 4000如果我说我只想要匹配123 4386
  3. 的东西
  4. 4000和4001,如果我希望它匹配123或3532
  5. 4001如果我希望它匹配3532但不匹配123。
  6. 到目前为止,我的想法涉及“选择articleid,其中tagid = 123和tagid = 4386”,但很明显,tagid字段不可能是同一记录中的两件事(使用“或”会得到结果,但是它不能确保它只是匹配123和4386的东西)。接下来,我将一次查询一个条件,然后使用PHP来过滤哪些文章应该/不应该匹配,但是在我的脑海里有一种唠叨的感觉,这应该可以轻松完成在数据库级别,我只是想不出如何(或谷歌用于什么)。我希望能够一次过滤1000个标签。

5 个答案:

答案 0 :(得分:2)

您可以为AND案例使用多个内部联接:

select distinct a.ArticleID
from   Articles as a
    inner join Articles AS b on a.ArticleID = b.ArticleID and b.TagID = 123
    inner join Articles AS c on a.ArticleID = c.ArticleID and c.TagID = 4386;

对于OR案例,您可以执行以下操作:

select distinct ArticleID
from   Articles
where TagID = 123 or TagID = 3532;

要一起完成所有这些案例,您可能最终会使用子查询:

select distinct a.ArticleID
from   Articles as a
where  exists (select * from Articles as b where a.ArticleID = b.ArticleID and b.TagID = 123);

然后,您可以使用常规SQL逻辑运算符(和,或,不)将多个exists条件连接在一起。对于大量标签来说,这很可能不会特别有效。

答案 1 :(得分:1)

您有三个查询选项:

  SELECT a.articleid
    FROM ARTICLES a
    JOIN TAGS t ON t.tagid = a.tagid
   WHERE t.tagid IN (123, 4386)
GROUP BY a.articleid
  HAVING COUNT(DISTINCT t.tagid) = 2

请注意,计数必须等于IN子句中的参数数量,以及DISTINCT的使用。如果没有这个区别,那么一篇文章与同一个标签有2个关联会被视为误报。这种方法也不像IMO那样轻松地转移到动态SQL ......

  SELECT a.articleid
    FROM ARTICLES a
    JOIN TAGS t1 ON t1.tagid = a.tagid
                AND t1.tagid = 123
    JOIN TAGS t2 ON t2.tagid = a.tagid
                AND t2.tagid = 4386
GROUP BY a.articleid

这可能是最快的选择。

SELECT a.articleid
  FROM ARTICLES a
 WHERE EXISTS(SELECT NULL
                FROM TAGS t
               WHERE t.tagid = a.tagid
                 AND t.tagid = 123)
   AND EXISTS(SELECT NULL
                FROM TAGS t
               WHERE t.tagid = a.tagid
                 AND t.tagid = 4386)

由于您的要求是动态的,我建议使用MySQL的准备语句:

DECLARE num INT 

SET @sql = 'SELECT a.articleid FROM ARTICLES a';

WHILE num > 0
  SET @sql = CONCAT(@sql, 'JOIN TAGS t', num, 'ON t', num,'.tagid = a.tagid AND t', num,'.tagid = ', tag, ' ');

  SET num = num - 1;
END WHILE;

SET @sql = CONCAT(@sql, 'GROUP BY a.articleid');

PREPARE stmt FROM @sql
EXECUTE stmt
DEALLOCATE PREPARE stmt;

如果您将num设置为零,您将收到所有文章。 WHILE循环将追加JOIN,创建一个包含的标签列表。如果您想在同一查询中支持排除项,这足以让您入门。

答案 2 :(得分:0)

您可以使用的第二个条件:

select t1.articleid from table t1, table t2 where t1.tagid = 123 and t2.tagid = 4386

答案 3 :(得分:0)

除了此处的其他答案之外,如果您可以构建使用子查询的查询,则可以通过将逗号分隔的标记ID字符串传递给表值函数来解决SQL通常无法处理数组的问题。该函数会将字符串分解为一个可以查询的整数表。使用它,您将能够查询(max-varchar-size / max-size-of-ID-as-string)标记;通常是数千人。

请原谅MSSQL语法;我不知道mysql。我希望它有表值函数(返回表的函数)或等价函数。

-- the 'OR' query
declare @taglist varchar(8000)
set @taglist = '1,2,3,4'

SELECT DISTINCT a.ArticleID FROM Article a
JOIN Tags t ON t.ArticleID = a.ArticleID
WHERE t.TagID IN (SELECT * FROM arrToTable(tagList))

Here's这样一个函数的样本。

答案 4 :(得分:0)

  1. SELECT DISTINCT ARTICLEID FROM myTable WHERE TagId=3532
  2. SELECT DISTINCT ARTICLEID FROM myTable WHERE TagId=123 AND TagId=4386
  3. SELECT DISTINCT ARTICLEID FROM myTable WHERE TagId=123 OR TagId=3532
  4. SELECT DISTINCT ARTICLEID FROM myTable WHERE TagId=3532 AND TagId <> 123