Mysql 3个表在一对多上联接

Mysql 3个表在一对多上联接,mysql,Mysql,我在学习连接时遇到一些困难,我正在处理2个一对多关系: 在这种情况下,我有许多章节和许多评级的小说 我需要获得小说信息,再加上与每部小说相关的章节数,以及每部小说的评分,我正在尝试: SELECT n.id , n.nvl_title , COUNT(c.id) AS nvl_chapters , AVG(nr.rate_value) as nvl_rating , MAX(c.createdAt) AS nvl_last_update FROM no

我在学习连接时遇到一些困难,我正在处理2个一对多关系:

在这种情况下,我有许多章节和许多评级的小说

我需要获得小说信息,再加上与每部小说相关的章节数,以及每部小说的评分,我正在尝试:

SELECT n.id
     , n.nvl_title
     , COUNT(c.id) AS nvl_chapters
     , AVG(nr.rate_value) as nvl_rating
     , MAX(c.createdAt) AS nvl_last_update
  FROM novels n
  left 
  JOIN novels_ratings nr 
    ON nr.novel_id = n.id
  left 
  JOIN chapters c 
    ON c.nvl_id = n.id 
   AND c.chp_status = 'Active'
 WHERE n.nvl_status IN ("Active", "Finished") 
 GROUP 
    BY n.id;
仅处理章节,查询似乎工作得很好,但如果我添加行“left JOIN Novelies\u ratings nr ON nr.Novely\u id=n.id”,章节计数将增加到小说的许多评级

例如:一本有两章和两个等级的小说总共返回4章

我们将非常感谢您的帮助

如果有什么我想解释的,请告诉我,我会尽力澄清

我正在使用一些丑陋的查询来完成这项工作,但一旦章节表开始有许多寄存器,我就不得不学习更多的光学查询

编辑

我创建了一个小数据库,足以对查询进行一些测试:

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";

CREATE TABLE `chapters` (
  `id` int(11) NOT NULL,
  `nvl_id` int(20) DEFAULT NULL,
  `chp_title` varchar(250) DEFAULT NULL,
  `chp_status` varchar(8) NOT NULL DEFAULT 'Active',
  `createdAt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


INSERT INTO `chapters` (`id`, `nvl_id`, `chp_title`, `chp_status`, `createdAt`) VALUES
(1, 1, 'generic chapter 1', 'Active', '0000-00-00 00:00:00'),
(2, 1, 'generic chapter 2', 'Active', '0000-00-00 00:00:00');

CREATE TABLE `novels` (
  `id` int(20) NOT NULL,
  `nvl_title` varchar(250) DEFAULT NULL,
  `nvl_status` varchar(8) NOT NULL DEFAULT 'Active'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


INSERT INTO `novels` (`id`, `nvl_title`, `nvl_status`) VALUES
(1, 'generic novel', 'Active');

CREATE TABLE `novels_ratings` (
  `id` int(20) NOT NULL,
  `novel_id` int(20) DEFAULT NULL,
  `rate_value` int(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `novels_ratings` (`id`, `novel_id`, `rate_value`) VALUES
(1, 1, 3),
(2, 1, 4);

ALTER TABLE `chapters`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `novels`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `novels_ratings`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `chapters`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

ALTER TABLE `novels`
  MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

ALTER TABLE `novels_ratings`
  MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
COMMIT;

多谢各位

这是一个完整的解决方案(最终)。由于MySQL没有实现
完全连接
,因此解决方案使用
左连接
右连接
配对

你可以做:

with
r as (
  select n.id, avg(nr.rate_value) as nvl_rating
  from novels n
  join novels_ratings nr on nr.novel_id = n.id
  group by n.id
),
c as (
  select n.id, count(c.id) as nvl_chapters, max(c.createdAt) as nvl_last_update
  from novels n
  join chapters c on c.nvl_id = n.id and c.chp_status = 'Active' 
  group by n.id
)
select r.id, r.nvl_rating, c.*
from r
left join c on c.id = r.id
UNION ALL
select c.id, r.nvl_rating, c.*
from r
right join c on c.id = r.id
where r.id is null

我认为最简单的方法是:

SELECT n.id, n.nvl_title,
       COUNT(c.id) AS nvl_chapters,
       (select AVG(nr.rate_value) from novels_ratings nr where nr.novel_id = n.id) as nvl_rating,
       MAX(c.createdAt) AS nvl_last_update
FROM novels n
left JOIN chapters c ON c.nvl_id = n.id AND c.chp_status = 'Active'
WHERE n.nvl_status IN ("Active", "Finished") 
GROUP BY n.id;

非常简单,而且它的性能也应该很好。

您需要分别处理这两个聚合。并且您需要一个完全连接。。。MySQL还不支持这一点。可以这样做,但查询将很难看。您可以使用此网站了解有关加入的信息。这个网站用例子解释连接方法。谢谢你的回答,我已经看过很多关于连接的教程,但是我找不到一个答案,关于为什么我做了两个连接,一个连接到章节,另一个连接到评分,为什么章节与小说中的许多相关项目(章节和评分)有关。相信我,在stackoverflow中写一个问题永远是我在几个小时的努力后做的最后一件事@刺客。我怎样才能做到你所说的一切?@Andresplazamarkés你说得对。我终于有时间写下整个解决方案了。嗨@Marc谢谢你的回答,我以前试过这个,效果很好,但我担心的是,novel_ratings表可能会有数百个条目,使得查询中的另一个SELECT会降低查询性能,正如chapter表联接对我来说似乎是一种非常有效的方式,这就是为什么我坚持使用两个联接来实现此查询(如果可能的话)如果表的索引正确,则这不应是低效的查询。但是您可以通过查询临时表并在其上进行连接,以非常类似的方式进行连接。由你决定。非常感谢@The Impaler,它看起来非常有趣和有用,我将对这个查询进行一些测试,分享结果,如果它是正确的,将你标记为正确答案。