MySQL: Putting single row results in seperate columns

Viewed 143

I am using MySQL version 5.7.31, but just installed a MySQL server version 8 in case its needed

I have a table containing a range of information about an entity, like this:

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

In another table i have the information of which skills belongs to which combination of name and age like this:

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

Which makes me able to select all skills for Jane, 25 - returning 2 rows. However i am required to put these into separate columns for each entity in a final view like below and i am struggling to figure out how to do this. The view should continue working when i get additional rows supplied in the tables shown above

+------+-----+-------------+-----------+--------+  
| Name | Age |   skill1    |  skill2   | skill3 |  
+------+-----+-------------+-----------+--------+  
| Bob  |  22 | programming | Na        | Na     |  
| Jack |  15 | Na          | Na        | Na     |  
| Jane |  25 | programming | marketing | Na     |  
+------+-----+-------------+-----------+--------+
5 Answers

You can use row_number() and conditional aggregation. Say the tables are called persons and person_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

It is unclear which logic you want to spread the skills over the columns. This assumes that you have an ordering column, called id, that can be used for this.

If you want these in separate columns in MySQL 5.7, probably the simplest method is correlated subqueries:

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;

This looks complicated, but the three subqueries are the same except for the offset.

Note: This returns NULL rather than 'NA' when no skills are available. That is the typical way of representing an unavailable value.

If you really needed 'NA', you can use 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,

An alternative in older versions is a hack using 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;

This aggregates all the skills into a string and then extracts the nth element.

For 'NA', you would use COALESCE() like above.

You can use GROUP_CONCAT aggregation function for get comma separated list of skills:

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
;

Here the fiddle SQLize.online

Result

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

In case with limited skills count next query can be used:

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

Okay, you doing it fundamentally WRONG here, this is NOT how you should treat a relational database, it's borderline abusive.

Relational database lesson 101, ALWAYS use indexed autoincremented primary key. ALWAYS! (Sorry to be harsh but working in to many systems designed by people that don't understanding this basics making performance crazy bad).

This is how your user table should look like.

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

Skills table

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

Link table

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

Okay now for the SQL itself, from your example it seems that you always want to have programing on skill1 and marketing as skill2, and in that case (we using max and group by to just get one row per user).

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

The best would obviously be to use group_concat as other has suggested but then you have to handle all the result in one column, but can handle unlimited number of skills.

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

First Create table structure in such a way you can use definite condition... search more over Pivot options

Here is an example of such scenario

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      |
+--------+-------+------+------------+--------+-----------+
Related