簡體   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