【问题标题】:GROUP a result BY a specific keyword in MySQL?通过 MySQL 中的特定关键字对结果进行分组?
【发布时间】:2011-11-12 12:53:37
【问题描述】:

我有一个页面用我正在搜索的关键字标记了多个标签,有时它没有用那个关键字标记,所以当它有这些标签时,它会返回如下结果,

查询,

SELECT*
FROM root_pages AS p

LEFT JOIN root_mm_pages_tags AS mm
ON mm.page_id = p.page_id

LEFT JOIN root_tags AS t
ON t.tag_id =  mm.tag_id
AND t.tag_name LIKE '%story%'

WHERE p.page_title LIKE '%article title 8%'
AND p.page_hide != '1'

ORDER BY (t.tag_name+0) ASC

结果,

page_id     page_url            tag_name    
17          article title 8     NULL
17          article title 8     NULL
17          article title 8     sys-rsv-story-1

所以我必须使用GROUP BY来解决这个问题,

SELECT*
FROM root_pages AS p

LEFT JOIN root_mm_pages_tags AS mm
ON mm.page_id = p.page_id

LEFT JOIN root_tags AS t
ON t.tag_id =  mm.tag_id
AND t.tag_name LIKE '%story%'

WHERE p.page_title LIKE '%article title 8%'
AND p.page_hide != '1'

GROUP BY p.page_id
ORDER BY (t.tag_name+0) ASC

它会返回类似的东西,

page_id     page_url            tag_name    
17          article title 8     NULL

但是我在这个结果之后,它有我正在搜索的 关键字

page_id     page_url            tag_name    
17          article title 8     sys-rsv-story-1

那么,是否可以按关键字对结果进行分组?或者其他更好的查询来归档这个?

此外,如果该关键字不存在,它不应该返回结果,但它仍然存在,

page_id     page_url            tag_name    
    17          article title 8     NULL
    17          article title 8     NULL

编辑:

我的新解决方案,

 SELECT*
FROM root_pages AS p

INNER JOIN root_mm_pages_tags AS mm
ON mm.page_id = p.page_id

INNER JOIN root_tags AS t
ON t.tag_id =  mm.tag_id

WHERE p.page_title LIKE '%{group1}%'
AND t.tag_name LIKE '%story%'
AND p.page_hide != '1'

AND EXISTS (
    SELECT page_url
    FROM root_pages AS p

    LEFT JOIN root_mm_pages_tags AS mm
    ON mm.page_id = p.page_id

    LEFT JOIN root_tags AS t
    ON t.tag_id =  mm.tag_id

    WHERE page_url = 'article title 1d'
    AND t.tag_name LIKE '%story%'
    AND p.page_hide != '1'
)

ORDER BY (t.tag_name+0) ASC

【问题讨论】:

    标签: php mysql group-by tagging sql-like


    【解决方案1】:

    尽量不要在 LEFT JOIN 中使用条件:

    SELECT *
    FROM root_pages AS p
    
    LEFT JOIN root_mm_pages_tags AS mm
    ON mm.page_id = p.page_id
    
    LEFT JOIN root_tags AS t
    ON t.tag_id =  mm.tag_id
    
    WHERE p.page_title LIKE '%article title 8%'
    AND p.page_hide != '1'
    AND t.tag_name LIKE '%story%'
    
    GROUP BY p.page_id
    ORDER BY (t.tag_name+0) ASC
    

    编辑:如果您想获取页面标题包含“文章标题”的行以及没有该标题但需要关键字的行,请使用此查询(如@user985935 建议的那样):

    SELECT *
    FROM root_pages AS p
    
    LEFT JOIN root_mm_pages_tags AS mm
    ON mm.page_id = p.page_id
    
    LEFT JOIN root_tags AS t
    ON t.tag_id =  mm.tag_id
    
    WHERE (p.page_title LIKE '%article title 8%'
    OR t.tag_name LIKE '%story%')
    AND p.page_hide != '1'
    
    
    GROUP BY p.page_id
    ORDER BY (t.tag_name+0) ASC
    

    【讨论】:

    • 那是我的初始查询,但我需要使这个查询动态地用于具有该关键字的行,也适用于没有该关键字的行。这有意义吗?谢谢。
    • 如果是这样的话,如果你使用 or 代替 and 条件如 (p.page_title LIKE '%article title 8%' OR t.tag_name LIKE '%story%') 只需修改上面提供的查询然后您将能够从中获得动态结果。 ;)
    • 感谢您的开斋节,德米特里! :-)
    【解决方案2】:

    哎呀。

    我认为您的 SQl 查询很奇怪。

    需要注意的几点:

    • 使用bar LIKE '%foo%' 对SQL 引擎来说非常困难,他必须顺序扫描所有行并在列栏中搜索子字符串'foo'。索引使用不可用。因此,如果可以,请避免它。如果可以的话,至少使用bar LIKE 'foo%'(如果你有开始的话,索引可用)。在你的情况下,你可能有一个标题为“文章标题 80”的页面匹配,你确定你不需要一个 p.page_title = 'article title 8' 吗?
    • 你为什么要按照指令的顺序做一个+0?您真的要防止使用索引吗?
    • p.page_hide != '1',p.page_hide 不是 tinyint?它是一个字符串?为什么要使用 UTF8 编码的字符来存储 0 或 1?

    但这不是问题。

    您的一个问题是,使用GROUP BY p.page_id 的组实际上在 SQL 中是错误的,但 MySQL 隐藏了这一事实。按指令分组应至少包含不是 SELECT 部分中的聚合的每个元素(聚合是计数或总和,或平均等)。在这里,您按 id 分组并获得一个随机的东西,MySQL 认为您知道自己在做什么,并且当 id 相同时,您确定 select 中的每个其他字段都相同(事实并非如此,tag_name不同)。

    如果您有多个标签匹配您的关键字(这里是“故事”),您不希望页面被多次列出吗?带所有标签?

    所以。

    您想选择一个页面,其中有一个标签。我会说使用 EXISTS 关键字让事情变得更简单。

    可能是这样的:

    SELECT * 
     FROM root_pages AS p
    WHERE p.page_title = 'article title 8'
     AND p.page_hide != 1
     -- exists will return true as soon as the engine find one matching row
     AND EXISTS (
      SELECT mm.page_id
      FROM root_mm_pages_tags AS mm
        LEFT JOIN root_tags AS t
          ON t.tag_id =  mm.tag_id
      -- here we make a correlation between the subquery and the main query
      WHERE mm.page_id = p.page_id
      AND t.tag_name LIKE '%story%'
    )
    

    但是通过这个查询,您只能获得页面名称,而不是标签结果。如果你想列出一个页面的所有匹配标签,你需要另一个查询,非常接近你所拥有的:

    SELECT p.page_id, p.page_name, t.tag_name
     FROM root_pages AS p
       INNER JOIN root_mm_pages_tags AS mm
           ON mm.page_id = p.page_id
         INNER JOIN root_tags AS t
             ON (t.tag_id =  mm.tag_id 
             AND t.tag_name LIKE '%story%')
    WHERE p.page_title = 'article title 8'
     AND p.page_hide != 1
    

    对于第一个INNER JOIN,我只保留有标签的页面。对于第二个INNER JOIN,我只保留来自root_mm_pages 的行,在root_tags 中具有匹配的标签。我认为您的 NULL 来自链接到其他不匹配标签的表中的行(因此在 root_tags 表结果中有 NULL 字段供您查询)。 因此,如果您只想匹配结果,请不要使用 LEFT JOIN

    如果您只希望每个表有一个结果,则需要GROUP BY p.page_id, p.page_name,并且需要在剩余字段t.tag_name 上添加聚合函数。您可以使用GROUP_CONTACT(t.tag_name ORDER BY t.tag_name ASC SEPARATOR ",") 获取此表的所有匹配标签的列表。

    编辑

    所以实际上您似乎想要具有匹配标题的页面 OR 具有匹配关键字的页面。在这种情况下,您应该使用LEFT JOIN,您将获得 NULL 值。如果您不需要结果中的标签,EXISTS 关键字仍然是您最好的朋友,只需将AND EXISTS 替换为OR EXISTS。这是最快的解决方案。

    如果您需要结果中的匹配标签,或者当它们不是标签时为 NULL,您有 2 个解决方案。 UNION 查询混合了标题的简单查询和带有内部连接的标签的查询的结果,或者使用 GROUP_CONCAT 进行漂亮的分组。如果您不使用 GROUP_CONCAT (如@Dmitry Teplyakov 的回答),您可能会获得页面标题不匹配的结果,只有关键字匹配,但 tag_name 字段将显示 NULL 作为在应用 GROUP BY 之前列出的第一个 tag_row查询上是一个NULL字段——页面为3个关键字,匹配的关键字不是查询中的第一个——。

    SELECT 
     p.page_id,
     p.page_name,
     GROUP_CONCAT(t.tag_name ORDER BY t.tag_name ASC SEPARATOR ",")
    FROM root_pages AS p
       LEFT JOIN root_mm_pages_tags AS mm
           ON mm.page_id = p.page_id
         LEFT JOIN root_tags AS t
             ON t.tag_id =  mm.tag_id 
    WHERE p.page_hide != 1
     AND (p.page_title = 'article title 8'
      OR t.tag_name LIKE '%story%')
    GROUP BY p.page_id, p.page_name;
    

    但是在这里我们通过 tag_name 取消了您的订单。按 tag_name 排序意味着如果它多次匹配关键字,您希望同一页面出现在多行中。或者如果名称匹配并且关键字也......或者可能不匹配。所以实际上 UNION 查询解决方案可能更好。但关键是你应该在 tag_name 字段中解释你想要什么:-)

    【讨论】:

    • 非常感谢您的回复,regilero。根据您的建议,我实际上得到了我想要的结果 - 请参阅上面的编辑。谢谢您的帮助! :-)
    • @lauthiamkok:看我的编辑,我不确定你真的有你想要的。取决于你真正想要什么。在 SQL 中小心使用似乎给出正确结果的查询,进行扩展测试用例。
    • regilero,感谢您的编辑。为了安全起见,我将代码更改为使用 INNER JOIN。是的,我需要制作扩展测试用例。谢谢! :-)
    • 但是如果你只使用INNER JOIN,那么它会删除标题匹配且没有关键字的页面,INNER JOIN 查询应该用在有匹配标题查询的UNION 查询中。
    • 也许我应该使用LEFT JOIN!大声笑在各种情况下进行了测试,到目前为止似乎还不错...我想我需要更多地了解LEFT JOININNER JOIN 之间的区别,因为我总是对它们感到困惑。非常感谢您的回答!:-)
    【解决方案3】:

    这是我在评论中提到的示例查询:

    SELECT *
    FROM root_pages AS p
    
    LEFT JOIN root_mm_pages_tags AS mm
    ON mm.page_id = p.page_id
    
    LEFT JOIN root_tags AS t
    ON t.tag_id =  mm.tag_id
    
    WHERE p.page_hide != '1'
    AND (t.tag_name LIKE '%story%' OR p.page_title LIKE '%article title 8%')
    GROUP BY p.page_id
    ORDER BY (t.tag_name+0) ASC
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多