简体   繁体   English

如何在两个以上的MySQL表上执行最佳FULL OUTER JOIN?

[英]How to perform optimal FULL OUTER JOIN on more than two MySQL tables?

At first, I would like to say that I already found out that MySQL does not support SQL's FULL OUTER JOIN . 首先,我想说的是,我已经发现MySQL不支持SQL的FULL OUTER JOIN However, I need data to be joined that way... Hope I am correct by saying "I need FULL OUTER JOIN". 但是,我需要以这种方式连接数据...希望我说对了,我说“我需要FULL OUTER JOIN”。 Correct me if I am wrong about that. 如果我错了,请纠正我。

What is my case about? 我的情况是什么? One table ( users ) describes users registered to play games. 一张桌子( users )描述了注册玩游戏的用户。 The table results1 describes results of users playing game1 , The table results2 describes results of users playing game2 and so on. results1描述了用户玩的Game1的结果,表results2描述了用户的结果玩游戏2等。
Now I want to write a query that gets list of users and user points from all games in the period of time. 现在,我想编写一个查询,以获取一段时间内所有游戏的用户和用户点的列表。 Points must be summed. 点数必须加总。 The query must group the results by user_id and date (monthly). 该查询必须按user_id和日期(每月)对结果进行分组。

The big problem is that none of tables have full set of months (so I cannot do just LEFT or RIGHT joins). 最大的问题是,任何一个表都没有完整的月份数(因此我不能仅执行LEFT或RIGHT连接)。 I thought about making some kind of temporary calendar table (just years and months in the period of time) and then join tables with points ( results1 , results2 , results3 , and so on..) to that calendar table. 我考虑过要制作某种类型的临时日历表(一段时间内只有几年和几个月),然后将带有点( results1results2results3等等)的表连接到该日历表。 But this kind of solution seems to be quite complicated as well. 但是这种解决方案似乎也很复杂。 Any other ideas? 还有其他想法吗?

My case (MySQL dump): 我的情况(MySQL转储):

-- --------------------------------------------------------
-- Host:                         192.168.0.60
-- Server version:               5.5.40-cll-lve - MySQL Community Server (GPL) by Atomicorp
-- Server OS:                    Linux
-- HeidiSQL Version:             9.1.0.4867
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;

-- Dumping database structure for example
CREATE DATABASE IF NOT EXISTS `example` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;
USE `example`;


-- Dumping structure for table example.results1
CREATE TABLE IF NOT EXISTS `results1` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `points` int(11) DEFAULT NULL,
  `date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

-- Dumping data for table example.results1: ~8 rows (approximately)
/*!40000 ALTER TABLE `results1` DISABLE KEYS */;
INSERT INTO `results1` (`id`, `user_id`, `points`, `date`) VALUES
    (1, 1, 5, '2014-01-17'),
    (2, 1, 5, '2014-01-18'),
    (3, 2, 10, '2014-02-17'),
    (4, 9, 8, '2014-03-17'),
    (5, 1, 15, '2014-07-17'),
    (6, 3, 9, '2014-10-17'),
    (7, 1, 20, '2015-02-17'),
    (8, 5, 10, '2014-06-17');
/*!40000 ALTER TABLE `results1` ENABLE KEYS */;


-- Dumping structure for table example.results2
CREATE TABLE IF NOT EXISTS `results2` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `points` int(11) DEFAULT NULL,
  `date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;

-- Dumping data for table example.results2: ~8 rows (approximately)
/*!40000 ALTER TABLE `results2` DISABLE KEYS */;
INSERT INTO `results2` (`id`, `user_id`, `points`, `date`) VALUES
    (1, 1, 50, '2014-01-01'),
    (2, 2, 35, '2014-01-02'),
    (3, 3, 14, '2014-01-03'),
    (4, 4, 18, '2014-06-01'),
    (5, 5, 16, '2014-06-01'),
    (6, 5, 16, '2014-06-02'),
    (7, 6, 4, '2014-10-29'),
    (8, 1, 20, '2014-01-16');
/*!40000 ALTER TABLE `results2` ENABLE KEYS */;


-- Dumping structure for table example.results3
CREATE TABLE IF NOT EXISTS `results3` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `points` int(11) DEFAULT NULL,
  `date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;

-- Dumping data for table example.results3: ~3 rows (approximately)
/*!40000 ALTER TABLE `results3` DISABLE KEYS */;
INSERT INTO `results3` (`id`, `user_id`, `points`, `date`) VALUES
    (1, 9, 6, '2014-12-17'),
    (2, 1, 10, '2014-01-01'),
    (3, 1, 2, '2014-10-17'),
    (4, 1, 8, '2014-01-03');
/*!40000 ALTER TABLE `results3` ENABLE KEYS */;


-- Dumping structure for table example.results4
CREATE TABLE IF NOT EXISTS `results4` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `points` int(11) DEFAULT NULL,
  `date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;

-- Dumping data for table example.results4: ~2 rows (approximately)
/*!40000 ALTER TABLE `results4` DISABLE KEYS */;
INSERT INTO `results4` (`id`, `user_id`, `points`, `date`) VALUES
    (1, 4, 41, '2015-03-17'),
    (2, 1, 2, '2014-12-17');
/*!40000 ALTER TABLE `results4` ENABLE KEYS */;


-- Dumping structure for table example.users
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

-- Dumping data for table example.users: ~10 rows (approximately)
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` (`id`, `name`) VALUES
    (1, 'Sophie'),
    (2, 'Joshua'),
    (3, 'Isabelle'),
    (4, 'Jack'),
    (5, 'Emily'),
    (6, 'Harry'),
    (7, 'Olivia'),
    (8, 'Oliver'),
    (9, 'Lily'),
    (10, 'Charlie');
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

The query I have for joining two tables: 我要联接两个表的查询:

    SELECT

        r1_r2.user_id, r1_r2.points, r1_r2.`date`

    FROM (

        SELECT
                IFNULL(rr1.user_id, rr2.user_id) as user_id, (IFNULL(rr1.points,0) + IFNULL(rr2.points,0)) as points, IFNULL(rr1.`date`, rr2.`date`) as `date`
            FROM (
                SELECT
                        r1.user_id, SUM(r1.points) as points, DATE_FORMAT(r1.`date`, '%Y-%m') as `date`
                    FROM results1 as r1
                    GROUP BY r1.user_id, DATE_FORMAT(r1.`date`, '%Y-%m')
            ) as rr1
            LEFT JOIN (
                SELECT
                        r2.user_id, SUM(r2.points) as points, DATE_FORMAT(r2.`date`, '%Y-%m') as `date`
                    FROM results2 as r2
                    GROUP BY r2.user_id, DATE_FORMAT(r2.`date`, '%Y-%m')
            ) as rr2 ON (rr1.user_id = rr2.user_id AND rr1.`date` = rr2.`date`)


        UNION


        SELECT
                IFNULL(rl1.user_id, rl2.user_id) as user_id, (IFNULL(rl1.points,0) + IFNULL(rl2.points,0)) as points, IFNULL(rl1.`date`, rl2.`date`) as `date`
            FROM (
                SELECT
                        r1.user_id, SUM(r1.points) as points, DATE_FORMAT(r1.`date`, '%Y-%m') as `date`
                    FROM results1 as r1
                    GROUP BY r1.user_id, DATE_FORMAT(r1.`date`, '%Y-%m')
            ) as rl1
            RIGHT JOIN (
                SELECT
                        r2.user_id, SUM(r2.points) as points, DATE_FORMAT(r2.`date`, '%Y-%m') as `date`
                    FROM results2 as r2
                    GROUP BY r2.user_id, DATE_FORMAT(r2.`date`, '%Y-%m')
            ) as rl2 ON (rl1.user_id = rl2.user_id AND rl1.`date` = rl2.`date`)

    ) as r1_r2

HAVING
    r1_r2.`date` BETWEEN '2014-01' AND '2014-12'
ORDER BY 
    r1_r2.user_id ASC, r1_r2.`date` ASC

It works for two tables ( results1 and results2 ), but the problem is that I need more than two tables to be joined the same way... 它适用于两个表( results1results2 ),但是问题是我需要两个以上的表以相同的方式进行连接...

I have some kind of solution for that (like nesting tables again and again..), but the problem is that the solution of mine becomes very complicated (long in length, very complicated in reading and understanding) as well. 我对此有某种解决方案(例如一遍又一遍嵌套表格),但是问题是我的解决方案也变得非常复杂(篇幅长,阅读和理解非常复杂)。 Also, there are chances on some additional tables coming up in the very near future. 此外,在不久的将来还有可能出现一些其他表格。 How the query will look like after additional 3 or 5 tables will add? 添加额外的3或5个表后,查询的外观如何? If I will continue doing the same kind of nested joins the whole query will become more and more complicated to read, understand, modify... 如果我继续进行相同的嵌套联接,则整个查询将变得越来越复杂,难以阅读,理解,修改...

Here is the query for 3 tables joined ( results1 , results2 , results3 ): 这是对3个联接的表( results1results2results3 )的查询:

SELECT

        r1_r2_r3.user_id, r1_r2_r3.points, r1_r2_r3.`date`

    FROM (

        SELECT

                IFNULL(r1_r2_l.user_id, r3_l.user_id) as user_id, (IFNULL(r1_r2_l.points,0) + IFNULL(r3_l.points,0)) as points, IFNULL(r1_r2_l.`date`, r3_l.`date`) as `date`

            FROM (

                # BEGIN. RESULT FROM BEFORE

                SELECT

                    r1_r2.user_id, r1_r2.points, r1_r2.`date`

                FROM (

                    SELECT
                            IFNULL(rr1.user_id, rr2.user_id) as user_id, (IFNULL(rr1.points,0) + IFNULL(rr2.points,0)) as points, IFNULL(rr1.`date`, rr2.`date`) as `date`
                        FROM (
                            SELECT
                                    r1.user_id, SUM(r1.points) as points, DATE_FORMAT(r1.`date`, '%Y-%m') as `date`
                                FROM results1 as r1
                                GROUP BY r1.user_id, DATE_FORMAT(r1.`date`, '%Y-%m')
                        ) as rr1
                        LEFT JOIN (
                            SELECT
                                    r2.user_id, SUM(r2.points) as points, DATE_FORMAT(r2.`date`, '%Y-%m') as `date`
                                FROM results2 as r2
                                GROUP BY r2.user_id, DATE_FORMAT(r2.`date`, '%Y-%m')
                        ) as rr2 ON (rr1.user_id = rr2.user_id AND rr1.`date` = rr2.`date`)


                    UNION


                    SELECT
                            IFNULL(rl1.user_id, rl2.user_id) as user_id, (IFNULL(rl1.points,0) + IFNULL(rl2.points,0)) as points, IFNULL(rl1.`date`, rl2.`date`) as `date`
                        FROM (
                            SELECT
                                    r1.user_id, SUM(r1.points) as points, DATE_FORMAT(r1.`date`, '%Y-%m') as `date`
                                FROM results1 as r1
                                GROUP BY r1.user_id, DATE_FORMAT(r1.`date`, '%Y-%m')
                        ) as rl1
                        RIGHT JOIN (
                            SELECT
                                    r2.user_id, SUM(r2.points) as points, DATE_FORMAT(r2.`date`, '%Y-%m') as `date`
                                FROM results2 as r2
                                GROUP BY r2.user_id, DATE_FORMAT(r2.`date`, '%Y-%m')
                        ) as rl2 ON (rl1.user_id = rl2.user_id AND rl1.`date` = rl2.`date`)
                ) as r1_r2
                # END. RESULT FROM BEFORE
            ) as r1_r2_l
            LEFT JOIN (
                SELECT
                        r3.user_id, SUM(r3.points) as points, DATE_FORMAT(r3.`date`, '%Y-%m') as `date`
                    FROM results3 as r3
                    GROUP BY r3.user_id, DATE_FORMAT(r3.`date`, '%Y-%m')
            ) as r3_l ON (r1_r2_l.user_id = r3_l.user_id AND r1_r2_l.`date` = r3_l.`date`)


    UNION


        SELECT

                IFNULL(r1_r2_r.user_id, r3_r.user_id) as user_id, (IFNULL(r1_r2_r.points,0) + IFNULL(r3_r.points,0)) as points, IFNULL(r1_r2_r.`date`, r3_r.`date`) as `date`

            FROM (

                # BEGIN. RESULT FROM BEFORE

                SELECT

                    r1_r2.user_id, r1_r2.points, r1_r2.`date`

                FROM (

                    SELECT
                            IFNULL(rr1.user_id, rr2.user_id) as user_id, (IFNULL(rr1.points,0) + IFNULL(rr2.points,0)) as points, IFNULL(rr1.`date`, rr2.`date`) as `date`
                        FROM (
                            SELECT
                                    r1.user_id, SUM(r1.points) as points, DATE_FORMAT(r1.`date`, '%Y-%m') as `date`
                                FROM results1 as r1
                                GROUP BY r1.user_id, DATE_FORMAT(r1.`date`, '%Y-%m')
                        ) as rr1
                        LEFT JOIN (
                            SELECT
                                    r2.user_id, SUM(r2.points) as points, DATE_FORMAT(r2.`date`, '%Y-%m') as `date`
                                FROM results2 as r2
                                GROUP BY r2.user_id, DATE_FORMAT(r2.`date`, '%Y-%m')
                        ) as rr2 ON (rr1.user_id = rr2.user_id AND rr1.`date` = rr2.`date`)


                    UNION


                    SELECT
                            IFNULL(rl1.user_id, rl2.user_id) as user_id, (IFNULL(rl1.points,0) + IFNULL(rl2.points,0)) as points, IFNULL(rl1.`date`, rl2.`date`) as `date`
                        FROM (
                            SELECT
                                    r1.user_id, SUM(r1.points) as points, DATE_FORMAT(r1.`date`, '%Y-%m') as `date`
                                FROM results1 as r1
                                GROUP BY r1.user_id, DATE_FORMAT(r1.`date`, '%Y-%m')
                        ) as rl1
                        RIGHT JOIN (
                            SELECT
                                    r2.user_id, SUM(r2.points) as points, DATE_FORMAT(r2.`date`, '%Y-%m') as `date`
                                FROM results2 as r2
                                GROUP BY r2.user_id, DATE_FORMAT(r2.`date`, '%Y-%m')
                        ) as rl2 ON (rl1.user_id = rl2.user_id AND rl1.`date` = rl2.`date`)
                ) as r1_r2
                # END. RESULT FROM BEFORE
            ) as r1_r2_r
            RIGHT JOIN (
                SELECT
                        r3.user_id, SUM(r3.points) as points, DATE_FORMAT(r3.`date`, '%Y-%m') as `date`
                    FROM results3 as r3
                    GROUP BY r3.user_id, DATE_FORMAT(r3.`date`, '%Y-%m')
            ) as r3_r ON (r1_r2_r.user_id = r3_r.user_id AND r1_r2_r.`date` = r3_r.`date`)


    ) as r1_r2_r3

HAVING
    r1_r2_r3.`date` BETWEEN '2014-01' AND '2014-12'
ORDER BY 
    r1_r2_r3.user_id ASC, r1_r2_r3.`date` ASC

...I think you could get what do I mean by saying that query becomes very complicated to understand if we continue with more tables joined the same way. ...我想通过说如果我们继续使用相同的方式继续连接更多的表,查询将变得非常复杂,这可以理解我的意思。

By the way, this is just a simplified version of a real situation. 顺便说一下,这只是真实情况的简化版本。 In reality, results1 , results2 , results3 and results4 are tables already got by joining other tables, calculating values in between... So the final query I have to work on is much more complicated than mentioned in the examples above. 在现实中, results1results2results3results4已经通过加入其他表,计算之间有值表...所以最终的查询我必须工作在比上面的例子中提到的要复杂得多。

The question of mine would be: Can I make the query for joining more than two tables shorter, easier to understand? 我的问题是: 我可以使联接两个以上表的查询更短,更容易理解吗?

I think you can do what you want using union all and aggregation. 我认为您可以使用union all和聚合来执行您想要的操作。 I think the following does what you want: 我认为以下是您想要的:

select user_id, year(date), month(date), sum(points1) as point31,
       sum(points2) as points2, sum(points3) as points3
from users u left join
     ((select r1.user_id, r1.date, r1.points as points1, NULL as points2, NULL as points3
       from results1 r1
      ) union all
      (select r2.user_id, r2.date, NULL as points1, r2.points as points2, NULL as points3
       from results2 r2
      ) union all
      (select r3.user_id, r3.date, NULL as points1, NULL as points2, r3.points as points3
       from results3 r3
      ) 
     ) r
     on u.id = r.user_id
group by user_id, year(date), month(date);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM