在 MySQL 中按特定关键字对结果进行分组?

发布于 2024-12-15 00:19:05 字数 2118 浏览 1 评论 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
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

所以我必须使用 < code>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

I have a page tagged with multiple tags with the keyword I am searching and sometimes it is not tagged with that keyword, so when it has that tags, it will return a result like this below,

query,

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

result,

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

so I have to use GROUP BY to solve this problem,

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

and it returns something like this,

page_id     page_url            tag_name    
17          article title 8     NULL

But I am after this result which it has the keyword that I am searching for,

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

So, is it possible to group a result by a keyword? Or other better queries to archive this?

Also, it should not return the result if that keyword isn't there, but it still does,

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

EDIT:

My new solution,

 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

拔了角的鹿 2024-12-22 00:19:05

尝试不在 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

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

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

Try to not use condition in 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

EDIT: If you want to fetch rows with page title contains 'article title' and rows that have not that titles but have needed keyword, use this query (As @user985935 suggested):

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
等待我真够勒 2024-12-22 00:19:05

欧查。

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

需要注意的几点:

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

但这不是问题。

您的问题之一是,使用 GROUP BY p.page_id 进行分组实际上在 SQL 中是错误的,但 MySQL 隐藏了这一事实。 group by 指令应至少包含 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 字段中解释你想要什么:-)

Oucha.

Your SQl queries are quite strange I think.

Several thing to notice:

  • using bar LIKE '%foo%' is very hard for the SQL engine, he must sequentially scan all rows and search the substring 'foo' in the column bar. Index usage is not available. So avoid it if you can. Use at least bar LIKE 'foo%' if you can (index available if you have the start). And in you case you could have pages with a title 'article title 80' matching, are you sure you do not simply need a p.page_title = 'article title 8'?
  • why do you make a +0 in the order by instruction? Do you really want to prevent index usage?
  • p.page_hide != '1', p.page_hide isn't a tinyint? it's a string? why using UTF8 encoded characters to store 0 or 1?

But this is not the problem.

One of your problem is that using a group by GROUP BY p.page_id is in fact wrong in SQL but MySQL hides this fact. A group by instruction should contain at least every element which is not an aggegate in the SELECT part (an aggregate is count or sum, or avg, etc). Here you group by id and you obtain a random thing, MySQL thinks you know what you're doing and you're sure every other field in the select is the same when the id is the same (which is not the case, the tag_name differs).

And if you have several tags matching your keyword ('story' here) don't you want the page to be listed several times? with all tags?

So.

You want to select a page, where you have a tag. I would say use EXISTS keyword and make things simplier.

It may be something like that:

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%'
)

But with this query you only obtain the page name, not the tag result. And if you want to list all matching tags for a page you need another query, quite near of what you have:

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

With the first INNER JOIN I'm only keeping pages which have tags. With the second INNER JOIN I'm only keeping rows from root_mm_pages having a matching tag in root_tags. I think your NULL came from rows in this tables linked to other unmatching tags (so having NULL field in root_tags table result for you query). So do not use LEFT JOIN if you only want matchings results.

If you want only one result for each table you will need a GROUP BY p.page_id, p.page_name and you will need to add an aggregate function on the remaining field t.tag_name. You could use GROUP_CONTACT(t.tag_name ORDER BY t.tag_name ASC SEPARATOR ",") to obtain a list of all matching tags for this table.

EDIT

So it seems in fact you want pages with matching title OR pages with matching keyword. In this case you should use LEFT JOIN, and you will have NULL values. If you do not need the tag in the result the EXISTS keyword is still your best friend, just replace the AND EXISTS with OR EXISTS. It's the fastest solution.

If you need the matching tags in the result or NULL when they were no tags you have 2 solutions. A UNION query mixing results from a simple query on titles and a query on tags with inner joins, or doing the nice group by with GROUP_CONCAT. If you do not use GROUP_CONCAT (as in @Dmitry Teplyakov answer) you will maybe obtain results where the page title did'nt match, only the keyword, but the tag_name field will show NULL as the first tag_row listed before the GROUP BY is applied on the query is a NULL field -- the page as 3 keywords, the matching keyword is not the first in the query --.

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;

But here we loose your order by tag_name. Ordering by tag_name means you wants the same page appearing in several rows if it is matching the keyword several times. Or if the name is matching and the keyword also... or maybe not. So in fact the UNION query solution is maybe better. But the key point is you should explain what you want in the tag_name field :-)

哆啦不做梦 2024-12-22 00:19:05

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

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

here is the sample query I'm mentioning on the comment:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文