【问题标题】:SQL many-to-many matchingSQL 多对多匹配
【发布时间】:2008-08-24 00:09:34
【问题描述】:

我正在为一个网站实施一个标记系统。每个对象有多个标签,每个标签有多个对象。这是通过维护一个表来实现的,每条记录有两个值,一个用于对象的 id 和标签。

我希望编写一个查询来查找与给定标签集匹配的对象。假设我有以下数据([object] -> [tags]* 格式)

apple -> fruit red food
banana -> fruit yellow food
cheese -> yellow food
firetruck -> vehicle red

如果我想匹配(红色),我应该得到苹果和救火车。如果我想匹配(水果,食物),我应该得到(苹果,香蕉)。

我如何编写一个 SQL 查询来做我想做的事?

@Jeremy Ruten,

感谢您的回答。使用的符号用于提供一些示例数据 - 我的数据库确实有一个表,每条记录有 1 个对象 id 和 1 个标签。

其次,我的问题是我需要获取与所有标签匹配的所有对象。用你的 OR 代替 AND 像这样:

SELECT object WHERE tag = 'fruit' AND tag = 'food';

运行时不产生任何结果。

【问题讨论】:

  • 您使用什么 RDBMS?你需要如何实现这个?存储过程?你想如何传递参数?或者您会在 DBMS 之外动态地编写 SQL 吗?在这种情况下,使用什么编程语言?

标签: sql many-to-many tagging


【解决方案1】:

给定:

  • 对象表(主键 ID)
  • objecttags 表(外键objectId,tagid)
  • 标签表(主键id)

    SELECT distinct o.*
      from object o join objecttags ot on o.Id = ot.objectid
                    join tags t on ot.tagid = t.id
     where t.Name = 'fruit' or t.name = 'food';
    

这似乎是倒退的,因为您想要 and,但问题是,2 个标签不在同一行上,因此,and 不会产生任何结果,因为单行不能既是水果又是食物。 此查询通常会产生重复,因为您将获得每个对象的 1 行,每个标签。

如果你真的想在这种情况下做一个和,你将需要一个group by,例如在你的查询中一个having count = <number of ors>

  SELECT distinct o.name, count(*) as count
    from object o join objecttags ot on o.Id = ot.objectid
                  join tags t on ot.tagid = t.id
   where t.Name = 'fruit' or t.name = 'food'
group by o.name
  having count = 2;

【讨论】:

  • 当数据库中有数百万行时,这是非常不理想的
【解决方案2】:

哦,天哪,我可能误解了您的原始评论。

在 SQL 中执行此操作的最简单方法是拥有三个表:

1) Tags ( tag_id, name )
2) Objects (whatever that is)
3) Object_Tag( tag_id, object_id )

然后,您几乎可以快速、轻松、高效地询问您想要的任何数据问题(前提是您的索引正确)。如果你想变得花哨,你也可以允许多词标签(有一种优雅的方式,还有一种不太优雅的方式,我能想到)。

我假设这就是你所拥有的,所以下面的 SQL 将起作用:

字面意思:

    SELECT obj 
      FROM object
     WHERE EXISTS( SELECT * 
                     FROM tags 
                    WHERE tag = 'fruit' 
                      AND oid = object_id ) 
       AND EXISTS( SELECT * 
                     FROM tags 
                    WHERE tag = 'Apple'
                      AND oid = object_id )

还有其他方法可以做到,例如:

SELECT oid
  FROM tags
 WHERE tag = 'Apple'
INTERSECT
SELECT oid
  FROM tags
 WHERE tag = 'Fruit'

【讨论】:

    【解决方案3】:

    @Kyle:您的查询应该更像:

    SELECT object WHERE tag IN ('fruit', 'food');

    您的查询正在查找标签既是水果又是食物的行,这是不可能的,因为该字段只能有一个值,不能同时有两个值。

    【讨论】:

      【解决方案4】:

      将 Steve M. 的建议与 Jeremy 的建议结合起来,您将获得包含您正在寻找的内容的单一记录:

      select object
      from tblTags
      where tag = @firstMatch
      and (
             @secondMatch is null 
             or 
             (object in (select object from tblTags where tag = @secondMatch)
           )
      

      现在,它不能很好地扩展,但它会得到你正在寻找的东西。我认为有一种更好的方法可以做到这一点,这样您就可以轻松拥有 N 个匹配项,而不会对代码产生很大影响,但它目前让我无法理解。

      【讨论】:

        【解决方案5】:

        我推荐以下架构。

        Objects: objectID, objectName
        Tags: tagID, tagName
        ObjectTag: objectID,tagID
        

        使用以下查询。

        select distinct
            objectName
        from
            ObjectTab ot
            join object o
                on o.objectID = ot.objectID
            join tabs t
                on t.tagID = ot.tagID
        where
            tagName in ('red','fruit')
        

        【讨论】:

          【解决方案6】:

          我建议让您的表格每条记录有 1 个标签,如下所示:

           apple -> fruit
           apple -> red
           apple -> food
           banana -> fruit
           banana -> yellow
           banana -> food
          

          那你就可以了

           SELECT object WHERE tag = 'fruit' OR tag = 'food';
          

          如果你真的想按照自己的方式做,你可以这样做:

           SELECT object WHERE tag LIKE 'red' OR tag LIKE '% red' OR tag LIKE 'red %' OR tag LIKE '% red %';
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-18
            • 2018-01-24
            • 2022-10-07
            • 1970-01-01
            • 2019-03-08
            相关资源
            最近更新 更多