【问题标题】:Many To Many Querying Tagging Objects多对多查询标记对象
【发布时间】:2012-12-18 03:12:34
【问题描述】:

我有这个数据库架构。我只是为了简单起见而调用我标记对象的东西:

用户

  • user_id
  • 登录

用户到对象表(多对多)

  • user_id
  • object_id

对象表

  • object_id
  • object_stuff

用 user_id 表标记的对象(多对多带有额外的列)

  • user_id
  • object_id
  • tag_id

标签表

  • tag_id
  • 标签名称

我一直在使用这样的查询来获取特定用户的对象以及对象的标签(如果用户标记了任何标签)。 如果您认为可以对此进行优化,请随时告诉我

@user_id = 'whatever user_id I want';
SELECT 
    o.object_id AS object_id,
    o.object_stuff AS object_stuff,
    IF (tags.tag_name IS NOT NULL, GROUP_CONCAT(tags.tag_name SEPARATOR '|'), 'N/A') AS tags
FROM  object_table AS o
LEFT OUTER JOIN  obj_to_users_table AS o2u ON o.object_id = o2u.object_id
LEFT OUTER JOIN  users AS u ON u.user_id = o2u.user_id 
LEFT OUTER JOIN  obj_to_tag_users AS o2tu ON o.object_id = o2tu.object_id AND u.user_id = o2tu.user_id
LEFT OUTER JOIN  tags AS t ON t.tag_id = o2t.tag_id 
WHERE u.user_id = @user_id GROUP BY o.object_id

这会产生如下结果:

object_id     object_stuff        tags
1             stuff1...           tag1|tag2|tag3
2             stuff2...           tag4|tag6|tag1
3             stuff3...           tag7|tag2|tag5

我的问题是,例如,我想搜索标记为“tag2”的对象,我应该得到:

object_id     object_stuff        tags
1             stuff1...           tag1|tag2|tag3
3             stuff3...           tag4|tag2|tag1

但相反,我丢失了该对象附带的其他标签:

object_id     object_stuff        tags
1             stuff1...           tag2
3             stuff3...           tag2

如何修改我的 WHERE 子句,以便获得标记为“tag2”的对象,同时在结果集中保留该对象的其他标签?

... WHERE u.user_id = @user_id AND t.tag_name LIKE '%tag2%' ...

【问题讨论】:

    标签: mysql


    【解决方案1】:

    首先,计算级联结果。

    接下来,针对这些连接的结果执行搜索以保留标签的分组:

    SELECT * 
    FROM
    (
        SELECT 
            o.object_id AS object_id,
            o.object_stuff AS object_stuff,
            IF (tags.tag_name IS NOT NULL, 
                GROUP_CONCAT(tags.tag_name SEPARATOR '|'), 'N/A') AS tags
        FROM  object_table AS o
        LEFT OUTER JOIN  obj_to_users_table AS o2u 
            ON o.object_id = o2u.object_id
        LEFT OUTER JOIN  users AS u 
            ON u.user_id = o2u.user_id 
        LEFT OUTER JOIN  obj_to_tag_users AS o2tu 
            ON o.object_id = o2tu.object_id AND u.user_id = o2tu.user_id
        LEFT OUTER JOIN  tags AS t ON t.tag_id = o2t.tag_id 
        WHERE u.user_id = @user_id 
        GROUP BY o.object_id
    ) sub
    WHERE sub.tags LIKE '%tag2%'
    

    编辑:


    这是一个使用EXISTS 的示例。我还将LEFT JOINs 切换为INNER JOINs,因为搜索特定标签应该消除任何objects 而没有tags

    虽然这个查询看起来更复杂,但它应该更有可能使用任何可用的索引。

    我还提到过使用一组额外的INNER JOINs 执行相同类型的查询,但是那个看起来会更复杂,因为您仍然必须使用子查询来使用DISTINCT 来获取消除额外的JOINs 引入的任何可能的重复项...所以我建议改用EXISTS...

    SELECT 
        o.object_id AS object_id,
        o.object_stuff AS object_stuff,
        GROUP_CONCAT(t.tag_name SEPARATOR '|') AS tags
    FROM  object_table AS o
    INNER JOIN  obj_to_users_table AS o2u 
        ON o.object_id = o2u.object_id
    INNER JOIN  users AS u 
        ON u.user_id = o2u.user_id 
    INNER JOIN  obj_to_tag_users AS o2tu 
        ON o.object_id = o2tu.object_id AND u.user_id = o2tu.user_id
    INNER JOIN tags AS t 
        ON t.tag_id = o2t.tag_id
    WHERE 
        u.user_id = @user_id 
        AND EXISTS (
            SELECT 1
            FROM tags AS targetTag
            WHERE 
                targetTag.tag_id = o2t.tag_id
                AND targetTag.tag_name = 'tag2'
        )
    GROUP BY o.object_id
    

    【讨论】:

    • 太棒了,谢谢。子查询是完成此任务的唯一方法吗?
    • @rclai89 可能还有其他几种方法可以做到这一点......您可以在执行GROUP_CONCAT 之前执行过滤,通过执行内部连接或使用EXISTS。使用这些方法,查询可能看起来更复杂一些,但它也能够利用索引,因此它可能会执行得更好......
    • 如果不麻烦你,你能告诉我那些替代查询是什么样的吗?或者向我展示此类查询的入门指南?
    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 2011-11-29
    • 2011-10-09
    • 1970-01-01
    • 2011-12-07
    • 2011-02-13
    相关资源
    最近更新 更多