【问题标题】:Left join multiple tables if foreign key is not null - mysql optimization如果外键不为空,则左连接多个表 - mysql 优化
【发布时间】:2015-03-05 19:34:16
【问题描述】:

有一些类似的问题,但没有一个符合我的情况。

SQL Optimization - Join different tables based on column value

How to JOIN on different tables based on column value

MySQL query to JOIN tables based on column values

MySQL: Use CASE/ELSE value as join parameter

MySQL query where JOIN depends on CASE

https://dba.stackexchange.com/questions/53301/mysql-getting-result-using-3-tables-and-case-statements

我有这种结构的通知表

CREATE TABLE `notifications` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `notificaiton_type_id` int(11) DEFAULT NULL,
  `table1_id` int(11) DEFAULT NULL,
  `table2_id` int(11) DEFAULT NULL,
  `table3_id` int(11) DEFAULT NULL,
  `table4_id` int(11) DEFAULT NULL,
  `table5_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `userIdIndex` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

和 5 个表,从 table1 到 table5,具有这些结构(其他相同:我将其设置为测试,不确定是否重要,但除了发布的字段之外,这些表(1 到 5)还有其他字段好吧,只是他们不参与查询,所以为了简单起见,我只是跳过了他们)

CREATE TABLE `table1` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(300) COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

table*_id 是表的外键:table1 - table5 具有一对多关系。

我应该根据user_id 选择通知。根据通知类型,适当的 table*_id 有一些值,其他 foreign_keys 为 null(顺便说一下,有 2 个甚至 3 个 table *_id 可以与 null 不同的通知类型)。最初的想法是,如果外键通过使用 CASE、WHEN 具有与 null 不同的某些值,则只有一个查询将连接这些表,但正如我从这个问题的答案中了解到的那样,

MySQL query where JOIN depends on CASE

在这种情况下不能使用。

表 table1-table5 会比较大,有几百万或几千万条记录。因此,如果外键为空,我不希望加入额外的 2-4 个表。另外,我不认为将查询分成 2 个主要部分更好,例如 - 首先获取通知,然后在循环中查找关联表的值。

所以,关键是只加入那些table*_id不为空的表,如果它可以在mysql中完成。

主要问题是实现这一目标的最有效方法是什么 - 获取通知信息及其相关表格数据。

连接所有表的一般查询是一个普通的左连接,像这样

EXPLAIN SELECT 
n.`id`,
n.`user_id`,
n.`table1_id`,
n.`table2_id`,
n.`table3_id`,
n.`table4_id`,
n.`table5_id`
//  other fields
    FROM
      notifications AS n 
      LEFT JOIN table1 AS t1 
        ON t1.`id` = n.`table1_id` 
      LEFT JOIN table2 AS t2
        ON t2.`id` = n.`table2_id`
      LEFT JOIN table3 AS t3
        ON t3.`id` = n.`table3_id`
      LEFT JOIN table4 AS t4
        ON t4.`id` = n.`table4_id` 
      LEFT JOIN table5 AS t5 
        ON t5.`id` = n.`table5_id` 
    WHERE user_id = 5

这里是 sql fiddle with data http://sqlfiddle.com/#!2/3bf8f/1/0

谢谢

【问题讨论】:

    标签: mysql join query-optimization


    【解决方案1】:

    为什么不对这个左连接查询使用 VIEW?

    这里有更多关于 View 性能的信息:Is a view faster than a simple query?

    假设您的查询工作正常,您可以从中创建视图:

    CREATE VIEW view_myView AS 
    SELECT 
    n.`id`,
    n.`user_id`,
    n.`table1_id`,
    n.`table2_id`,
    n.`table3_id`,
    n.`table4_id`,
    n.`table5_id`
        FROM
          notifications AS n 
          LEFT JOIN table1 AS t1 
            ON t1.`id` = n.`table1_id` 
          LEFT JOIN table2 AS t2
            ON t2.`id` = n.`table2_id`
          LEFT JOIN table3 AS t3
            ON t3.`id` = n.`table3_id`
          LEFT JOIN table4 AS t4
            ON t4.`id` = n.`table4_id` 
          LEFT JOIN table5 AS t5 
            ON t5.`id` = n.`table5_id` 
        WHERE user_id = 5
    

    然后您只需通过以下方式访问此视图中的数据:

    SELECT * FROM view_myView;
    

    而且它应该比每次都调用查询更快。

    如你所见,写起来也短得多。

    【讨论】:

    • 能否请您提供一个小例子——提示在这种情况下如何使用? tks
    【解决方案2】:

    使用单个 ID 作为外键而不是要查询哪个表的列不是更有意义吗:

    CREATE TABLE `notifications` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `notification_type_id` int(11) DEFAULT NULL,
      `table_id` int(11) DEFAULT NULL,
      `table_name` VARCHAR(10) DEFAULT NULL
    ...
    

    然后你可以选择在哪个表中查询你需要的实际数据。

    SELECT `table_id`,`table_name` FROM `notifications`;
    SELECT * FROM @table_name WHERE `id`=@table_id;
    

    在这种情况下不需要昂贵的 LEFT JOIN,两个查询(或作为存储过程的复合查询)将不需要外键上的大索引,从而简化结构。它还具有可扩展的优势,例如,如果您需要第 6、7 或 100 个分区表怎么办?

    【讨论】:

    • 感谢您的回答,这可以工作,只是我不喜欢 2 个查询的想法,因为如果必须,那么也许不需要将表的名称保存在数据库中,之后根据 notification_type_id 进行第一个查询,我已经知道应该加入哪些表。除非这可以在 mysql 查询本身中完成?
    【解决方案3】:

    我认为你无所顾忌。 MySQL 将按原样处理您的查询,而无需您付出更多努力。

    你说:

    如果外键为空,我不希望加入额外的 2-4 个表。

    好消息:MySQL 不会。

    会看到notifications表中的key为null,看到你要加入的对应表中没有记录,然后继续。我什至不确定您想象它可能会尝试做什么来优化您,但您的查询已经按原样进行了优化。

    如果您已经在运行此查询并且遇到性能问题,那么您的问题可能在其他地方。在这种情况下,请提供更多信息。特别是,您的// other fields 行实际上可能比您想象的影响更大,具体取决于其他字段所在的位置。

    【讨论】:

    • 其实我也觉得可能是我想多了))),但是今天做错事,tmrrow 会带来性能问题的想法让我三思而后行。最初我认为mysql不会尝试加入表,因为外键为空,但它让我想到它,因为解释语句显示加入发生,即使扫描的行数是1。实际上我只是基准测试有 170 万行(不能再多)(对于每 1-5 个表),执行时间约为 0.02 秒,所以我想毕竟你是对的。谢谢
    • 其他字段我确定没什么大不了的,它可以是简单的数据,名称 - varchar 200-300,状态 - tinyint,日期 - 日期时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-29
    • 2015-03-13
    • 1970-01-01
    • 1970-01-01
    • 2012-08-05
    • 1970-01-01
    • 2015-09-28
    相关资源
    最近更新 更多