通过MySQL中的特定关键字对结果进行分组?

时间:2011-11-12 12:53:38

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

我的网页标记了多个标签,其中包含我正在搜索的关键字,有时候它没有标记该关键字,所以当它有这个标签时,它会返回如下所示的结果,

查询,

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

3 个答案:

答案 0 :(得分:2)

尽量不要在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

编辑:如果您要获取页面标题包含“文章标题”的行以及没有该标题但需要关键字的行,请使用此查询(As @ 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

答案 1 :(得分:1)

Oucha。

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

需要注意几件事:

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

但这不是问题。

你的一个问题是,GROUP BY p.page_id使用一个组实际上在SQL中是错误的,但是MySQL隐藏了这个事实。按指令分组应至少包含SELECT部分​​中不是aggegate的每个元素(聚合是count或sum,或avg等)。在这里你按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_pagesroot_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执行nice组。如果您不使用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字段中解释你想要的东西: - )

答案 2 :(得分:0)

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

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