1

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

用户

  • 用户身份
  • 登录

用户到对象表(多对多)

  • 用户身份
  • object_id

对象表

  • object_id
  • object_stuff

使用 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%' ...
4

1 回答 1

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. 我还将 s 切换LEFT JOININNER JOINs,因为搜索特定标签应该可以消除任何objects标签tags

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

我还提到过使用一组额外的INNER JOINs 来执行相同类型的查询,但是这样看起来会更加复杂,因为您仍然必须使用子查询来DISTINCT消除额外JOIN的 s引入的任何可能的重复项...所以我建议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
于 2012-12-19T18:40:02.877 回答