如果外键不为null,则左连接多个表 - mysql优化

时间:2015-01-07 19:43:43

标签: mysql join query-optimization

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

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选择通知。根据通知类型,适当的表* _id有一些值,其他foreign_keys为null(顺便说一下,通知类型为2甚至3 table *_id可以与null不同)。最初的想法是有一个只能连接那些表的查询,如果外键通过使用CASE,WHEN,它的某些值与null不同,但正如我从这个问题的答案中学到的那样,

MySQL query where JOIN depends on CASE

在这种情况下不能使用它。

表table1-table5将会相对较大,有数百万或数十万条记录。因此,如果外键为null,我不想加入额外的2-4个表。此外,我认为将查询分成两个主要部分并不是更好,例如 - 首先获取通知然后在循环中查找关联表的值。

所以,重点是只加入那些table*_id不是null的表,如果可以在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

由于

3 个答案:

答案 0 :(得分:1)

我认为你无所畏惧。 MySQL会照原样处理您的查询,而不需要您的任何努力。

你说:

  

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

好消息:MySQL不会。

notifications表中会看到该键为空,看到您要加入的相应表中没有记录,然后继续前进。我甚至不确定你想象的可能会尝试优化,但你的查询已经过优化了。

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

答案 1 :(得分:0)

为什么不将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;

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

如你所见,写作也要短得多。

答案 2 :(得分:0)

使用单个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个分区表,该怎么办?