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

Left join multiple tables if foreign key is not null - mysql 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

我有通知 table 这种结构

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个tables,从table1到table5,具有这些结构(其他相同:我设置这个是为了测试,不确定是否重要,但是那些 tables (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 是 tables 的外键:table1 - table5 具有一对多关系。

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

MySQL query where JOIN depends on CASE

在这种情况下不能使用。

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

所以,重点是只加入 table 那些 table*_id 不是空的,如果它可以在 mysql 中完成的话。

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

连接到所有 table 的一般查询是通常的左连接,像这样

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 和数据 http://sqlfiddle.com/#!2/3bf8f/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;

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

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

使用单个 ID 作为外键,然后使用 table 要查询的列是否更有意义:

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 which table查询您需要的实际数据。

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

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

我看你是多虑了。 MySQL 将按原样处理您的查询,无需您做任何更多努力。

您说:

I would not prefer to join extra 2-4 tables if foreign keys are null.

好消息:MySQL不会。

会看到notificationstable中key为null,看到你加入的对应table中没有记录,然后继续.我什至不确定你想象的它可能会尝试做你正在尝试优化的事情,但你的查询已经按原样进行了优化。

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