【问题标题】:Struggling to structure an SQL query努力构建 SQL 查询
【发布时间】:2021-07-21 18:44:57
【问题描述】:

目前,我正在尝试运行一个查询,该查询返回每个已捕获特定类型的所有唯一“物种”的培训师的“培训师”用户名和“类型”标题。例如,如果有两个不同的物种在打架,而 Jenny 都抓住了他们,那么她应该这样输出:

 ---------------------
| Username |   Type   |
 ---------------------
|  Jenny   | Fighting |
 ---------------------

如果她还捕获了另一种类型的所有不同物种,则会输出另一行,但类型不同。这必须考虑到一个物种有两种类型,并且训练员可能会捕获一个以上的每个物种,因此要确定某人是否捕获了一个类型的所有物种,两种类型都被考虑在内。表格如下所示:

    (Type)                (Species)                  (Trainer)                (Pokemon)
 ------------    ----------------------------     ---------------      ------------------------
| id | title |  | id | title | type1 | type2 |   | id | username |    | id | species | trainer |
 ------------    ----------------------------     ---------------      ------------------------

我还提供了查询的架构和示例数据。在它下面有一个预期的结果集。到目前为止,我已经采取了一种方法来确定每种类型的独特物种有多少,使用以下查询:

SELECT Type.id AS TypeID, Type.title, COUNT(Species.id) AS 'Number of Species of Type'
FROM Type, Species WHERE Type.id = Species.type1 OR Type.id = Species.type2 GROUP BY Type.id;

我的下一个想法是确定训练师捕获的每种类型的物种数量,以便我可以比较两者。但我坚持如何构建查询来做到这一点。也不能使用视图或公用表表达式。任何建议或想法将不胜感激。

create database pokemon;
use pokemon;

CREATE TABLE IF NOT EXISTS `Type` (
  `id` TINYINT NOT NULL,
  `title` VARCHAR(50) NOT NULL,
  PRIMARY KEY (`id`));

CREATE TABLE IF NOT EXISTS `Species` (
  `id` TINYINT UNSIGNED NOT NULL,
  `title` VARCHAR(50) NOT NULL,
  `type1` TINYINT NOT NULL,
  `type2` TINYINT NULL,
  PRIMARY KEY (`id`),
    FOREIGN KEY (`type1`) REFERENCES `Type` (`id`),
    FOREIGN KEY (`type2`) REFERENCES `Type` (`id`));

CREATE TABLE IF NOT EXISTS `Trainer` (
  `id` INT NOT NULL,
  `username` VARCHAR(50) NOT NULL,
  PRIMARY KEY (`id`));

CREATE TABLE IF NOT EXISTS `Pokemon` (
  `id` BIGINT NOT NULL,
  `species` TINYINT UNSIGNED NOT NULL,
  `trainer` INT NULL,
  PRIMARY KEY (`id`),
    FOREIGN KEY (`trainer`) REFERENCES `Trainer` (`id`),
    FOREIGN KEY (`species`) REFERENCES `Species` (`id`));


insert into Type values (1,'Normal');
insert into Type values (2,'Fighting');
insert into Type values (3,'Flying');
insert into Type values (4,'Poison');
insert into Type values (5,'Ground');
insert into Type values (6,'Rock');
insert into Type values (7,'Bug');
insert into Type values (8,'Ghost');
insert into Type values (9,'Steel');
insert into Type values (10,'Fire');
insert into Type values (11,'Water');
insert into Type values (12,'Grass');
insert into Type values (13,'Electric');
insert into Type values (14,'Psychic');
insert into Type values (15,'Ice');
insert into Type values (16,'Dragon');
insert into Type values (17,'Dark');
insert into Type values (18,'Fairy');

insert into Species values (100,'Voltorb',13,null);
insert into Species values (101,'Electrode',13,null);
insert into Species values (102,'Exeggcute',12,14);
insert into Species values (103,'Exeggutor',12,14);
insert into Species values (104,'Cubone',5,null);
insert into Species values (105,'Marowak',5,null);
insert into Species values (106,'Hitmonlee',2,null);
insert into Species values (107,'Hitmonchan',2,null);
insert into Species values (108,'Lickitung',1,null);
insert into Species values (109,'Koffing',4,null);
insert into Species values (110,'Weezing',4,null);
insert into Species values (111,'Rhyhorn',5,6);
insert into Species values (112,'Rhydon',5,6);
insert into Species values (113,'Chansey',1,null);
insert into Species values (114,'Tangela',12,null);
insert into Species values (115,'Kangaskhan',1,null);
insert into Species values (116,'Horsea',11,null);
insert into Species values (117,'Seadra',11,null);
insert into Species values (118,'Goldeen',11,null);
insert into Species values (119,'Seaking',11,null);
insert into Species values (120,'Staryu',11,null);
insert into Species values (121,'Starmie',11,14);

insert into Trainer values (1,'Ash');
insert into Trainer values (2,'Brock');
insert into Trainer values (3,'Misty');
insert into Trainer values (4,'Jenny');
insert into Trainer values (5,'Luna');

insert into Pokemon values (1,109,1);
insert into Pokemon values (2,110,1);
insert into Pokemon values (3,115,1);
insert into Pokemon values (4,113,1);
insert into Pokemon values (5,108,1);
insert into Pokemon values (6,117,1);
insert into Pokemon values (7,102,2);
insert into Pokemon values (8,103,2);
insert into Pokemon values (9,121,2);
insert into Pokemon values (10,104,2);

insert into Pokemon values (11,111,3);
insert into Pokemon values (12,112,3);
insert into Pokemon values (13,121,3);

insert into Pokemon values (14,106,4);
insert into Pokemon values (15,107,4);
insert into Pokemon values (16,110,4);

样本数据的预期结果集是:

 ---------------------
| Username |   Type   |
| --------------------|
|   Ash    |  Poison  |
| --------------------|
|   Ash    |  Normal  |
| --------------------|
|   Brock  |  Psychic |
| --------------------|
|   Misty  |   Rock   |
| --------------------|
|   Jenny  | Fighting |
 --------------------

【问题讨论】:

  • MistyGround 不匹配,它被分配给该类型的 2 个物种,而该类型包含 4 个物种。
  • 但是Misty 匹配到Rock...
  • 请不要通过破坏您的帖子为他人增加工作量。通过在 Stack Exchange 网络上发帖,您已在 CC BY-SA 4.0 license 下授予 Stack Exchange 分发该内容的不可撤销的权利(即无论您未来的选择如何)。根据 Stack Exchange 政策,帖子的非破坏版本是分发的版本。因此,任何破坏行为都将被撤销。如果您想了解更多关于删除帖子的信息,请参阅:How does deleting work?
  • 也不要试图破坏您收到的答案。

标签: mysql sql


【解决方案1】:
WITH 
cte1 AS ( SELECT Species.id sid, t1.id, t1.title ttitle
          FROM Species
          JOIN Type t1 ON Species.type1 = t1.id
          UNION ALL
          SELECT Species.id sid, t2.id, t2.title ttitle
          FROM Species
          JOIN Type t2 ON Species.type2 = t2.id ),
cte2 AS ( SELECT ttitle, COUNT(id) ids
          FROM cte1
          GROUP BY ttitle ),
cte3 AS ( SELECT Trainer.username, cte1.ttitle, COUNT(*) ids
          FROM Pokemon
          JOIN Trainer ON Pokemon.trainer = Trainer.id
          JOIN cte1 ON Pokemon.species = cte1.sid 
          GROUP BY Trainer.username, cte1.ttitle )
SELECT cte3.username, cte2.ttitle type
FROM cte2
JOIN cte3 USING (ttitle, ids);

对于 MySQL 5.x,没有 CTE 的情况相同

SELECT cte3.username, cte2.ttitle type
FROM ( SELECT ttitle, COUNT(id) ids
       FROM ( SELECT Species.id sid, t1.id, t1.title ttitle
              FROM Species
              JOIN Type t1 ON Species.type1 = t1.id
              UNION ALL
              SELECT Species.id sid, t2.id, t2.title ttitle
              FROM Species
              JOIN Type t2 ON Species.type2 = t2.id) AS cte1
              GROUP BY ttitle ) AS cte2
      JOIN ( SELECT Trainer.username, cte1.ttitle, COUNT(*) ids
             FROM Pokemon
             JOIN Trainer ON Pokemon.trainer = Trainer.id
             JOIN ( SELECT Species.id sid, t1.id, t1.title ttitle
                    FROM Species
                    JOIN Type t1 ON Species.type1 = t1.id
                    UNION ALL
                    SELECT Species.id sid, t2.id, t2.title ttitle
                    FROM Species
                    JOIN Type t2 ON Species.type2 = t2.id) AS cte1 ON Pokemon.species = cte1.sid 
      GROUP BY Trainer.username, cte1.ttitle) AS cte3 USING (ttitle, ids);

https://dbfiddle.uk/?rdbms=mysql_8.0&rdbms2=mysql_5.7&fiddle=0a33601724fa53d9ee4a583c983ff01a

【讨论】:

  • 不需要在 cte2 中嵌套 cte1 吗? 这是Species 表的非规范化结构的影响。 难道你不能像你那样从 cte3 访问 cte1 的属性吗? 这两个副本必须是独立的。
  • 这在更大的输入样本上给出了错误的答案,可能是因为训练员能够捕捉到每个独特物种的多个神奇宝贝。
  • @BBQSquirrel 不要破坏这个答案。
  • 在这种情况下,你应该用(从口袋妖怪中选择不同的物种,训练师)替换口袋妖怪条目,我认为它会没问题。
  • 你指的是哪个部分?
【解决方案2】:

如果是这样的情况,训练师可以多次使用同一个物种,当然,这不应该算作两个物种,你只需要用没有重复的表版本替换表中的神奇宝贝,意思:

(select distinct species, trainer from pokemon)

所以查询将是:

select t.username, tp.title
from
(select p.trainer, t.id TypeID, count(species) Cuenta
from (select distinct species, trainer from pokemon) p, species s, type t
where p.species = s.id
and (s.type1 = t.id or s.type2 = t.id)
group by p.trainer, t.id) Tra
, (SELECT Type.id AS TypeID, Type.title, COUNT(Species.id) Cuenta
FROM Type, Species WHERE Type.id = Species.type1 OR Type.id = Species.type2
GROUP BY Type.id) ty
, trainer t, type tp
where ty.TypeID = Tra.TypeId
    and ty.Cuenta = Tra.Cuenta
    and t.id = Tra.Trainer
    and tp.id = Tra.TypeId;

为了测试结果,我添加了这三个Ground类型的记录; Specie Cubone 到 Brock:

insert into Pokemon values (17,104,2);
insert into Pokemon values (18,104,2);
insert into Pokemon values (19,104,2);

我观察到,在前面的查询中,Brock 会在 Ground 中显示为证书,而在这个新的查询中则不会。

PS.:您可以在 Akina's query 中进行相同的替换,我相信它也可以正常工作。

【讨论】:

    【解决方案3】:

    我真的很喜欢Akina's solution。它组织得很好,很漂亮。我的看起来不太好,但是,以防万一你想要另一种方法:

    select t.username, tp.title 
    from
    (select p.trainer, t.id TypeID, count(species) Cuenta
    from pokemon p, species s, type t
    where p.species = s.id
    and (s.type1 = t.id or s.type2 = t.id)
    group by p.trainer, t.id) Tra
    , (SELECT Type.id AS TypeID, Type.title, COUNT(Species.id) Cuenta
    FROM Type, Species WHERE Type.id = Species.type1 OR Type.id = Species.type2 
    GROUP BY Type.id) ty
    , trainer t, type tp
    where ty.TypeID = Tra.TypeId 
        and ty.Cuenta = Tra.Cuenta
        and t.id = Tra.Trainer 
        and tp.id = Tra.TypeId 
    

    Tra 是关于每个培训师的课程。 ty 是您在关于每种类型有多少物种的问题中提出的查询。

    【讨论】:

    • 他的解决方案不适用于更大的输入集,这是不正确的。
    • 你的解决方案我相信有同样的问题,你没有考虑到训练师可以捕捉到多个相同物种的口袋妖怪,导致输出错误的结果。
    猜你喜欢
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 2019-08-26
    • 2018-09-14
    • 1970-01-01
    • 2014-06-04
    • 1970-01-01
    相关资源
    最近更新 更多