【问题标题】:MySQL - Merging two queries that have different conditions and limitsMySQL - 合并具有不同条件和限制的两个查询
【发布时间】:2021-08-04 17:17:16
【问题描述】:

我正在为我的网站实现一个标签系统,使用 PHP + MySQL。

在我的数据库中,我有三个表:

帖子

Id Title DateTime

主键:ID

标签

Id Tag Slug
1 First Tag first-tag

主键:ID |关键:蛞蝓

标签地图

Id Tag

主键:两者

(Id = 帖子中的帖子 ID;标签 = 标签中标签的 ID)

例如,给定网址 www。 ... .net/tag/first-tag,我需要显示:

  • 标签的名称(在本例中:“第一个标签”);
  • 最近发布的 30 个具有该标签的帖子。

为了实现这一点,我使用了两个不同的查询:

首先

SELECT Tag FROM Tags WHERE Slug = ? LIMIT 1

然后

SELECT p.Title FROM Posts p, Tags t, TagsMap tm
WHERE p.Id = tm.Id
AND p.DateTime <= NOW()
AND t.Id = tm.Tag
AND t.Slug = ?
ORDER BY p.Id DESC
LIMIT 30

但我认为这在性能方面不是一个好的解决方案(如果我错了,请纠正我)。

所以,我的问题是:如何(如果可能)将这两个查询合并为一个?

提前感谢您的建议。

【问题讨论】:

  • 您不应使用基于逗号的连接。使用join 并定义与ons 的关系。你已经有t.Slug = ? 那么你为什么需要第一个查询呢?使用更大数据集的 SQL fiddle 可能会有所帮助。
  • @user3783243 我需要第一个查询来获取标签名称,以便将其显示为页面标题。但我是第一个说这不是一个好的解决方案。
  • 为什么您的每个表都没有唯一的主键?
  • @Martin 我已经编辑了我的问题(见表格)。
  • t.name 应该有它的标签。在你的第二个选择中包含它,第一个不需要

标签: mysql


【解决方案1】:

您上面显示的查询不是最佳解决方案,因为它首先创建所有表的cartesian product,然后根据条件过滤掉数据。如果这些表将来变得更重,那么您的查询将开始变慢 (SLOW QUERIES)。

请在此方法上使用连接。前任。 INNER JOINLEFT JOINRIGHT JOIN

试试这个 SQL:

SELECT t.*, p.* FROM Tags t 
INNER JOIN TagsMap tm ON (tm.Tag = t.Id )
INNER JOIN Posts p ON (p.Id = tm.Id AND p.DateTime <= NOW())
WHERE t.slug LIKE 'First Tag'
ORDER BY p.Id DESC
LIMIT 30

【讨论】:

  • 使用此查询,我为每个帖子获得两倍的标签(“第一个标签”)。我只需要获取一次标签(页面标题),而不是显示的每篇文章。
  • @Salvino:MySQL 的查询优化器不会为旧式 SQL-89 连接语法做笛卡尔积。就像使用 INNER JOIN 时一样,它会执行嵌套循环连接。使用更现代的语法是个好主意,但实际上它们的优化相同。
  • @BillKarwin 我想我们弄错了。你说你得到了两次标签,但 slug 在所有标签中都是独一无二的,对吧?还是不是?
  • 每个标签都有自己独特的 Slug(“第一个标签”=>“第一个标签”、“第二个标签”=>“第二个标签”等等)。
  • 当你想只返回两边都有对的记录时,你将使用INNER JOIN,当你需要“左”表中的所有记录时,你将使用LEFT JOIN,不管他们是否在“正确”表中有对子。检查this 以获得更好的理解。
【解决方案2】:

鉴于您以一种可以利用外键并将它们与对应项匹配的方式构建表,那么您可以在查询中使用JOIN

SELECT
    Tags.Tag,
    Posts.title
FROM
    Tags
LEFT JOIN
    TagsMap ON Tags.id = TagsMap.tag
LEFT JOIN
    Posts ON TagsMap.id = Posts.id AND
    Posts.DateTime <= NOW()
WHERE
    Posts.id = TagsMap.id AND
    Tags.Slug = ?
ORDER BY
    Posts.id DESC
LIMIT 30

这个想法是优化查询,但您需要在视图中以编程方式过滤结果集,以便仅显示一次 Tag

【讨论】:

  • 使用这个查询(我添加了缺少的 WHERE 子句),我得到了每个帖子的标签('第一个标签')。我只需要获取一次标签(针对页面标题),而不是针对显示的每个帖子。
  • @AbsoluteBeginner 尝试使用子查询的更新版本。
  • 新查询返回错误结果:帖子没有想要的标签。
  • 在 SQL 方面,没有办法只为一个字段返回一次结果并继续其余的。您将始终获得所有关联,即标签将始终是结果的一部分。如果您希望它只显示一次,则由您来过滤它(无论是背面还是正面)。因为在您的结果集中,每个标题都与一个标签和 slug 相关联。
【解决方案3】:

如果每个“帖子”最多有一个“slug”,请将slug 作为一列包含在Posts 中。

如果每个“帖子”可以有任意数量的“标签”,那么就有一个表格

CREATE Tags (
    post_id ... NOT NULL, 
    tag VARCHAR(..)... NOT NULL,
    post_dt DATETIME NOT NULL,
    PRIMARY KEY(post_id),
    INDEX(tag, dt)
) ENGINE=InnoDB

您可能想使用LEFT JOIN TagsGROUP_CONCAT(tag)

我不知道您所说的“first_tag”中的“first”是什么意思。也许你应该摆脱“第一”?

给定标签的最后 30 个帖子:

SELECT p.*,
       ( SELECT GROUP_CONCAT(tag) FROM Tags ) AS tags
    FROM ( SELECT post_id  FROM tags  WHERE tag = ?
              ORDER BY post_dt DESC  LIMIT 30 ) AS x
    JOIN posts AS p ON p.id = x.post_id
 
   

【讨论】:

  • 如果我理解正确,您建议只使用两个表(帖子和标签),而我选择了所谓的“毒液解决方案”(3 个表)。正确的?每个标签都有一个 Slug 的原因是后者可能由几个单词组成并带有重音字母(例如 black cafè -> black-cafe)。 “first-tag”只是 Slug 格式的一个示例。
  • P.S.每个帖子都可能有一个或多个与之关联的标签。
  • @AbsoluteBeginner -(LEFT 允许零标签。)对于重音符号,合适的“排序规则”将自动看到 cafè == cafe == Cafe == CAFE。在广义搜索中,连字符更难处理。
  • WordPress(仅举个例子)仅使用该格式作为标签 slug(website/tag/slug-of-the-tag)。但是您没有回答我:您是否提出了基于两个表(帖子和标签)的解决方案?如果是这样,我从哪里获得可用标签的列表?
  • 在新闻提要中,“slug”是一串神秘的字母,用于唯一标识新闻报道——与不唯一的“标签”完全不同。我不熟悉WP的含义;听起来它是一个经过消毒的版本,避免了重音、大写字母、标点符号等。很容易以编程方式将“标签”变成这样的“slug”,但反之则不然。
猜你喜欢
  • 1970-01-01
  • 2012-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多