MySQL:将单行结果放在单独的列中

MySQL: Putting single row results in seperate columns

我正在使用 MySQL 版本 5.7.31,但刚刚安装了一个 MySQL 服务器版本 8 以备不时之需

我有一个 table 包含关于实体的一系列信息,如下所示:

+------+-----+  
| Name | Age |  
+------+-----+  
| Bob  |  22 |  
| Jack |  15 |  
| Jane |  25 |    
+------+-----+

在另一个 table 中,我有关于哪些技能属于哪个姓名和年龄组合的信息,如下所示:

+-------------+------+-----+  
|    Skill    | Name | Age |  
+-------------+------+-----+  
| programming | Bob  |  22 |  
| programming | Jane |  25 |  
| marketing   | Jane |  25 |  
+-------------+------+-----+

这让我能够 select 25 岁的简的所有技能 - 返回 2 行。但是,我需要在如下所示的最终视图中将这些放入每个实体的单独列中,我正在努力弄清楚如何做到这一点。当我在上面显示的 tables

中获得额外的行时,视图应该继续工作
+------+-----+-------------+-----------+--------+  
| Name | Age |   skill1    |  skill2   | skill3 |  
+------+-----+-------------+-----------+--------+  
| Bob  |  22 | programming | Na        | Na     |  
| Jack |  15 | Na          | Na        | Na     |  
| Jane |  25 | programming | marketing | Na     |  
+------+-----+-------------+-----------+--------+

您可以使用 row_number() 和条件聚合。假设这些表称为 personsperson_skills:

select p.name, p.age, 
    max(case when ps.rn = 1 then ps.skill end) skill1,
    max(case when ps.rn = 2 then ps.skill end) skill2,
    max(case when ps.rn = 3 then ps.skill end) skill3
from persons p
left join (
    select ps.*, row_number() over(partition by name, age order by id) rn
    from person_skills ps
) ps on ps.name = p.name and ps.age = p.age
group by p.name, p.age

不清楚您希望将哪些逻辑分布到列中。这假设您有一个名为 id 的排序列,可用于此。

您可以使用 GROUP_CONCAT 聚合函数来获取逗号分隔的技能列表:

select
    Users.Name,
    Users.Age,
    GROUP_CONCAT(Skill) AS Skills
from Users
left join Skills on Skills.Name = Users.Name
group by Users.Name, Users.Age
;

这里是fiddleSQLize.online

结果

Name    Age    Skills
Bob     22     programming
Jack    15     NULL
Jane    25     programming,marketing

如果技能计数有限,可以使用下一个查询:

select
    Users.Name,
    Users.Age,
    GROUP_CONCAT(if(Skill='programming', 'programming', NULL)) AS Skill1,
    GROUP_CONCAT(if(Skill='marketing', 'marketing', NULL)) AS Skill2
from Users
left join Skills on Skills.Name = Users.Name
group by Users.Name, Users.Age
;

Fiddle here

首先以可以使用确定条件的方式创建 table 结构...在 Pivot 选项

中搜索更多内容

下面是这种情况的一个例子

Create table Person (FName varchar(20), LName varchar(20), Age Int);

Insert into Person values 
('Jacke','W',29),
('Prince','P',30),
('Rush','B',29);


Create table Speciality (FName varchar(20), Skill varchar(20),Technology varchar(30));

Insert into Speciality values 
('Jacke','Skill1','Java'),
('Jacke','Skill2','Unix'),
('Jacke','Skill3','PHP'),
('Prince','Skill1','.Net'),
('Prince','Skill2','Python'),
('Prince','Skill3','Notepad++'),
('Rush','Skill1','Office 365'),
('Rush','Skill2','.Net');

select P.FName, p.LName, p.Age,
  MAX(IF(Skill = 'Skill1', Technology, NULL)) AS Skill1,
  MAX(IF(Skill = 'Skill2', Technology, NULL)) AS Skill2,
  MAX(IF(Skill = 'Skill3', Technology, NULL)) AS Skill3
from Person P, Speciality S
where P.FName=S.Fname
group by P.FName, p.LName, p.Age;

+--------+-------+------+------------+--------+-----------+
| FName  | LName | Age  | Skill1     | Skill2 | Skill3    |
+--------+-------+------+------------+--------+-----------+
| Jacke  | W     |   29 | Java       | Unix   | PHP       |
| Prince | P     |   30 | .Net       | Python | Notepad++ |
| Rush   | B     |   29 | Office 365 | .Net   | NULL      |
+--------+-------+------+------------+--------+-----------+

如果你想在 MySQL 5.7 中将它们放在单独的列中,可能最简单的方法是相关子查询:

select p.*,
       (select ps.skill
        from person_skills ps
        where ps.name = p.name and ps.age = p.age
        order by ps.skill
        limit 1 offset 0
       ) as skill_1,
       (select ps.skill
        from person_skills ps
        where ps.name = p.name and ps.age = p.age
        order by ps.skill
        limit 1 offset 1
       ) as skill_2,
       (select ps.skill
        from person_skills ps
        where ps.name = p.name and ps.age = p.age
        order by ps.skill
        limit 1 offset 2
       ) as skill_3
from persons p;

这看起来很复杂,但是三个子查询除了offset.

外都是一样的

注意:当没有可用技能时,此 returns NULL 而不是 'NA'。这是表示不可用值的典型方式。

如果你确实需要'NA',你可以使用COALESCE():

coalesce( (select ps.skill
           from person_skills ps
           where ps.name = p.name and ps.age = p.age
           order by ps.skill
           limit 1 offset 0
          ), 'NA') as skill_1,

旧版本中的替代方法是使用 group_concat():

select p.name, p.age,
       substring_index(group_concat(ps.skill order by ps.skill), ',', 1) as skill_1,
       substring_index(substring_index(group_concat(ps.skill order by ps.skill), ',', 2), ',', -1) as skill_2,
       substring_index(substring_index(group_concat(ps.skill order by ps.skill), ',', 3), ',', -1) as skill_3
from persons p left join
     person_skills ps
     on ps.name = p.name and ps.age = p.age
group by p.name, p.age;

这会将所有技能聚合到一个字符串中,然后提取第 nth 个元素。

对于 'NA',您可以像上面那样使用 COALESCE()

好吧,你在这里做的根本是错误的,这不是你应该如何对待关系数据库,这是近乎滥用的。

关系数据库第 101 课,始终使用索引自增主键。总是! (很抱歉,我太苛刻了,但是在许多由不了解这些基础知识的人设计的系统中工作,这使得性能变得非常糟糕)。

这就是您的用户 table 的样子。

+----+------+-----+  
| id | Name | Age |  
+----+------+-----+  
|  1 | Bob  |  22 |  
|  2 | Jack |  15 |  
|  3 | Jane |  25 |    
+----+------+-----+

技能table

+----+--------------+
| id | skill        |
+----+--------------+
|  1 | programming  |
|  2 | Marketing    |
+----+--------------+

Link table

+----+---------+--------+
| id | skillId | userId |
+----+---------+--------+
|  1 |       1 |      3 |
|  2 |       2 |      3 |
|  3 |       1 |      1 |
+----+---------+--------+

好的,现在 SQL 本身,从你的例子来看,你似乎总是希望在 skill1 和 marketing 作为 skill2 上进行编程,在这种情况下(我们使用 max 和 group by 来获得一个每个用户的行数)。

SELECT 
    u.name Name, u.age Age, 
    MAX(CASE WHEN s.id = 1 THEN s.skill ELSE NULL END) Skill1, -- Programmer 
    MAX(CASE WHEN s.id = 2 THEN s.skill ELSE NULL END) Skill2  -- Marketing
FROM 
    users u
LEFT JOIN 
    linktable l ON l.userId = u.id
LEFT JOIN 
    skills s ON s.id = l.skillsId
GROUP BY 
    u.name, u.age

最好的办法显然是像其他人建议的那样使用 group_concat 但是你必须在一列中处理所有结果,但可以处理无限数量的技能。

SELECT 
    u.name Name, u.age Age, 
    GROUP_CONCAT(s.skill) Skills
FROM 
    users u
LEFT JOIN 
    linktable l ON l.userId = u.id
LEFT JOIN 
    skills s ON s.id = l.skillsId
GROUP BY 
    u.name, u.age