【问题标题】:Database design to enable Multiple tags like Stackoverflow?数据库设计以启用 Stackoverflow 等多个标签?
【发布时间】:2013-09-03 15:33:47
【问题描述】:

我有以下表格。

文章
a_id INT 主唯一
名称 VARCHAR
描述 VARCHAR
c_id INT

类别
id INT
cat_name VARCHAR

现在我只是使用

SELECT a_id,name,Description,cat_name FROM Articles LEFT JOIN Category ON Articles.a_id=Category.id WHERE c_id={$id}

这给了我属于某个类别的所有文章以及类别名称。
每篇文章只有一个类别

并且我以类似的方式使用子类别(我有另一个名为 sub_cat 的表)。
但每篇文章不一定都有子类别。它可能属于多个类别。

我现在想用 多个类别 标记一篇文章,就像标记 stackoverflow 上的问题一样(例如:使用 PHP、MYSQL、SQL 等多个标签)。

以后我必须显示(过滤)所有带有特定标签的文章(例如:用 php、php + MySQL 标记),我还必须显示标签以及文章名称、描述。
谁能帮我重新设计数据库?(我在后端使用 php + MySQL)

【问题讨论】:

标签: mysql database database-design database-schema junction-table


【解决方案1】:

创建一个新表:

CREATE TABLE ArticleCategories(
    A_ID INT,
    C_ID INT,
    Constraint PK_ArticleCategories Primary Key (Article_ID, Category_ID)
)

(这是 SQL 服务器的语法,可能与 MySQL 略有不同)

这称为“连接表”或“映射表”,它是您在 SQL 中表达多对多关系的方式。因此,每当您想向文章添加类别时,只需 INSERT 在此表中添加一行,其中包含文章和类别的 ID。

例如,你可以这样初始化它:

INSERT Into ArticleCategories(A_ID,C_ID)
    SELECT A_ID,C_ID From Articles

现在您可以从“文章”表中删除 c_id

要获取单个文章的所有类别,您可以使用如下查询:

SELECT a_id,name,Description,cat_name 
FROM Articles 
LEFT JOIN  ArticleCategories ON Articles.a_id=ArticleCategories.a_id 
INNER JOIN Category ON ArticleCategories.c_id=Category.id 
WHERE Articles.a_id={$a_id}

或者,返回所有具有 LIKE 特定字符串类别的文章:

SELECT a_id,name,Description
FROM Articles 
WHERE EXISTS(   Select * 
                From ArticleCategories 
                INNER JOIN Category ON ArticleCategories.c_id=Category.id 
                WHERE Articles.a_id=ArticleCategories.a_id 
                  AND Category.cat_name LIKE '%'+{$match}+'%'
             )

(你可能需要调整最后一行,因为我不确定字符串参数是如何传递 MySQL+PHP 的。)

【讨论】:

  • 我应该将与某篇文章关联的所有类别都存储到 ArticleCategories 中吗?即例如:A_ID 1 和 C_ID 的 3、4、6 我在 ArticleCategories 中有 3 行?
  • @Shrikanth 是的,完全正确。
  • @JoelBrown 是的,这是通常的做法。
  • @Shrikanth 这执行得很好,这是在关系/SQL 数据库中执行此操作的唯一正确方法。我向您保证,您正在考虑的任何其他方式都会在 98% 的情况下表现更差,并且在 99.9% 的情况下编码和维护会更糟糕。这就是 SQL/关系数据库应该的工作方式,所有 SQL 产品都是基于这一点构建的。
  • @RaymondNijland 是的,MySQL 在 `CREATE TABLE 语法中确实有一个 CONSTRAINT 关键字:dev.mysql.com/doc/refman/5.1/en/create-table.html 它就在 PRIMARY KEY 小节中。如果您有更好的方法来搜索包含字符串的字符串,则 LIKE '%string%' 是满足 OP 规定的要求所必需的,请告诉我它是什么。至于声称 EXISTS 会扼杀性能的说法,请提供参考,以及具有相同功能且不存在此问题的查询。
【解决方案2】:

好吧,RBarryYoung 你问我关于你得到的参考/分析

此参考/分析基于关闭 MySQL 服务器的文档/源代码分析

INSERT Into ArticleCategories(A_ID,C_ID)
    SELECT A_ID,C_ID From Articles

在包含许多行的大型 Articles 表上,此副本会将 CPU 中的一个核心推至 100% 负载,并会创建一个基于磁盘的临时表,这会降低整个 MySQL 的性能,因为磁盘会因该副本而受到压力. 如果这是一个一次性的过程,那还不错,但是如果您每次都运行它,请进行数学计算..

SELECT a_id,name,Description
FROM Articles 
WHERE EXISTS(   Select * 
                From ArticleCategories 
                INNER JOIN Category ON ArticleCategories.c_id=Category.id 
                WHERE Articles.a_id=ArticleCategories.a_id 
                  AND Category.cat_name LIKE '%'+{$match}+'%'
             )

注意不要把 sqlfriddle 上的执行时间当成是一个繁忙的服务器,而且时间变化很大以做出一个好的陈述,但是看看 View Execution Plan 有什么要说的

http://sqlfiddle.com/#!2/48817/21 演示

如果您有一个包含许多记录的大型 Articles 表,这两个查询总是会触发对表 Articles 和两个 DEPENDENT SUBQUERYS 的完整表扫描。 这意味着即使您只需要该类别中的文章,性能也取决于文章行数。

Select * 
                From ArticleCategories 
                INNER JOIN Category ON ArticleCategories.c_id=Category.id 
                WHERE Articles.a_id=ArticleCategories.a_id 
                  AND Category.cat_name LIKE '%'+{$match}+'%'

此查询是内部子查询,但当您尝试运行它时,MySQL 无法运行,因为它依赖于 Articles 表的值,因此这是相关子查询。一个子查询类型,将为外部查询处理的每一行计算一次。确实不好

还有更多方法可以重写 RBarryYoung 查询,我将展示一个。 即使使用 LIKE 运算符,INNER JOIN 方式也更有效 注意,我养成了一个习惯,即我从记录数最少的表开始,如果你从表 Articles 开始,我会按照我的方式向上工作,如果 MySQL 优化器选择正确的计划,执行将是相同的。 .

SELECT 
   Articles.a_id
 , Articles.name
 , Articles.description
FROM 
 Category

INNER JOIN
 ArticleCategories
ON
 Category.id = ArticleCategories.c_id

INNER JOIN
 Articles
ON 
 ArticleCategories.a_id = Articles.a_id

WHERE 
 cat_name LIKE '%php%';
;

请参阅http://sqlfiddle.com/#!2/43451/23 以查看演示请注意,这看起来更糟,因为它看起来需要检查更多行

请注意,如果 Article 表的记录数较少,RBarryYoung EXIST 方式和 INNER JOIN 方式将根据执行时间或多或少地执行相同的操作,并且更多证据表明当记录数变大时,INNER JOIN 方式可以更好地扩展

http://sqlfiddle.com/#!2/c11f3/1 EXISTS oeps 现在需要检查更多文章记录(即使它们没有与 ArticleCategories 表链接),因此现在查询效率较低 http://sqlfiddle.com/#!2/7aa74/8INNER JOIN 跟第一个demo一样的解释方案

关于扩展的额外说明,当您还想 ORDER BY 或 GROUP BY 时,NOT EXIST 方式更有可能创建一个基于磁盘的临时表,这会降低 MySQL 的性能

让我们也分析 EXIST 方式和 INNER JOIN 方式的 LIKE '%php%' vs = 'php'

EXIST 方式

http://sqlfiddle.com/#!2/48817/21/http://sqlfiddle.com/#!2/c11f3/1(更多文章)解释告诉我两种模式或多或少相同,但 'php' 应该快一点,因为 TYPE 列中的 const 类型与 ref 不同,但 LIKE %php%将使用更多 CPU,因为需要运行字符串比较算法。

INNER JOIN 方式

http://sqlfiddle.com/#!2/43451/23/http://sqlfiddle.com/#!2/7aa74/8(更多文章)解释告诉我 LIKE '%php%' 应该更慢,因为需要再分析 3 行,但在这种情况下不会令人震惊(你可以看到索引是并没有真正以最佳方式使用)。

RBarryYoung 方法有效,但至少不能在 MySQL 服务器上保持性能 见http://sqlfiddle.com/#!2/b2bd9/1http://sqlfiddle.com/#!2/34ea7/1 对于将在具有大量记录的大型表上进行扩展的示例,这是主题启动者所需要的

【讨论】:

  • "注意我已经养成了一个习惯,我从记录数最少的表开始":MySQL 的优化器真的是 那个 i> 不好?顺便说一句:我非常有信心 Oracle 和 Postgres 将为内部连接和存在的替代方案选择相同的执行计划。
  • MySQL 优化器与更成熟的数据库(如 Oracle 和 PostgreSQL)相比是转储的,并且无法优化 EXIST 和 INNER JOIN 具有相同的执行计划(糟糕)NOT IN 和 NOT EXISTS 也是 MySQL 的性能杀手,我认为这就是为什么更成熟的人(40/50 岁以上)(无意侮辱)为不成熟的数据库系统(如 MySQL)编写错误查询的原因,因为他们习惯于旧的子查询语法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-20
  • 2017-11-16
  • 2011-01-22
  • 2016-05-18
  • 2010-11-30
  • 2017-01-18
  • 1970-01-01
相关资源
最近更新 更多