繁体   English   中英

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

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

首先,我想说的是,我已经发现MySQL不支持SQL的FULL OUTER JOIN 但是,我需要以这种方式连接数据...希望我说对了,我说“我需要FULL OUTER JOIN”。 如果我错了,请纠正我。

我的情况是什么? 一张桌子( users )描述了注册玩游戏的用户。 results1描述了用户玩的Game1的结果,表results2描述了用户的结果玩游戏2等。
现在,我想编写一个查询,以获取一段时间内所有游戏的用户和用户点的列表。 点数必须加总。 该查询必须按user_id和日期(每月)对结果进行分组。

最大的问题是,任何一个表都没有完整的月份数(因此我不能仅执行LEFT或RIGHT连接)。 我考虑过要制作某种类型的临时日历表(一段时间内只有几年和几个月),然后将带有点( results1results2results3等等)的表连接到该日历表。 但是这种解决方案似乎也很复杂。 还有其他想法吗?

我的情况(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 */;

我要联接两个表的查询:

    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

它适用于两个表( results1results2 ),但是问题是我需要两个以上的表以相同的方式进行连接...

我对此有某种解决方案(例如一遍又一遍嵌套表格),但是问题是我的解决方案也变得非常复杂(篇幅长,阅读和理解非常复杂)。 此外,在不久的将来还有可能出现一些其他表格。 添加额外的3或5个表后,查询的外观如何? 如果我继续进行相同的嵌套联接,则整个查询将变得越来越复杂,难以阅读,理解,修改...

这是对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

...我想通过说如果我们继续使用相同的方式继续连接更多的表,查询将变得非常复杂,这可以理解我的意思。

顺便说一下,这只是真实情况的简化版本。 在现实中, results1results2results3results4已经通过加入其他表,计算之间有值表...所以最终的查询我必须工作在比上面的例子中提到的要复杂得多。

我的问题是: 我可以使联接两个以上表的查询更短,更容易理解吗?

我认为您可以使用union all和聚合来执行您想要的操作。 我认为以下是您想要的:

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