如何在MySQL中透视表

时间:2018-02-19 00:01:58

标签: mysql sql pivot-table

我有一个关于如何在MySQL中转动表格的问题。 我有一个数据集,像这样的列:

ID   Name     job_title
1    Sam       Fireman
2    Tomas     Driver
3    Peter     Fireman
4    Lisa      Analyst
5    Marcus    Postman
6    Stephan   Analyst
7    Mary      Research Manager
8    Albert    Analyst
9    Chen      Driver
...etc...

我想生成一个这样的表:

Fireman  Driver   Analyst  Postman   Research Manager ...
Sam     Tomas     Lisa     Marcus     Mary
Peter   Chen      Stephan  (someone)  (someone)...
....etc...

因为,这只是数据集中的一个示例,所以我可能不知道数据集中有多少不同的职位。目标是列出不同职位名称列中的每个人。

有没有办法做到这一点?或者是否可以在MySQL中生成这样的表? 一位工程师告诉我,它可以通过创建视图来完成,但我不知道如何。我看了一些书,仍然感到困惑。

欢迎任何想法和SQL查询指南!

5 个答案:

答案 0 :(得分:0)

有三件事需要考虑1)如何动态生成一堆max(2时的情况)分配一些东西来分组情况 - 在这种情况下我使用变量生成一个行号3 )您的一些职位名称包含空格,我删除该空格以生成列标题

set @sql = 
            (select concat('select ', gc,            ' from 
             (select name,job_title,
                if (job_title <> @p, @rn:=1 ,@rn:=@rn+1) rn,
                @p:=job_title p
                from t
                cross join (select @rn:=0,@p:=null) r
                order by job_title
              ) s group by rn;') from
            (select 
                group_concat('max(case when job_title = ', char(39),job_title ,char(39),' then name else char(32) end ) as ',replace(job_title,char(32),'')) gc
                from
                (
                select distinct job_title from t
                ) s
                ) t
             )
;           

生成此sql代码

select max(case when job_title = 'Fireman' then name else char(32) end ) as Fireman,
        max(case when job_title = 'Driver' then name else char(32) end ) as Driver,
        max(case when job_title = 'Analyst' then name else char(32) end ) as Analyst,
        max(case when job_title = 'Postman' then name else char(32) end ) as Postman,
        max(case when job_title = 'Research Manager' then name else char(32) end ) as ResearchManager
         from 
             (select name,job_title,
                if (job_title <> @p, @rn:=1 ,@rn:=@rn+1) rn,
                @p:=job_title p
                from t
                cross join (select @rn:=0,@p:=null) r
                order by job_title
              ) s group by rn;

哪个可以提交给动态sql

prepare sqlstmt from @sql;
execute sqlstmt;
deallocate prepare sqlstmt;

结果

+---------+--------+---------+---------+-----------------+
| Fireman | Driver | Analyst | Postman | ResearchManager |
+---------+--------+---------+---------+-----------------+
| Sam     | Tomas  | Lisa    | Marcus  | Mary            |
| Peter   | Chen   | Stephan |         |                 |
|         |        | Albert  |         |                 |
+---------+--------+---------+---------+-----------------+
3 rows in set (0.00 sec)

答案 1 :(得分:0)

我也在HackerRank上遇到了这个问题。尽管我认为group_concat的回答非常好,并且通常在MySql的早期版本中用于此类关键情况,但我发现concatgroup_concat可能难以阅读和理解。

如果您的MySql版本支持 window函数,那么您可以使用临时表解决此问题,因为MySql不支持外部联接。您需要为每个枢轴列使用一个单独的临时表,以避免窗口规范中不允许使用窗口函数错误:

use test;
drop table if exists occupations;
create table if not exists occupations  (
    name varchar(50)
    ,occupation varchar(50)
);
insert into occupations (name, occupation) select 'Samantha', 'Doctor'
    union all select 'Julia', 'Actor'
    union all select 'Maria', 'Actor'
    union all select 'Meera', 'Singer'
    union all select 'Ashley', 'Professor'
    union all select 'Kelly', 'Professor'
    union all select 'Christeen', 'Professor'
;
-- the way to approach this in mysql is to create a temp table with ordinals.
-- then upsert with four queries using row_number()
-- nb full join not supported. let's try temp table
drop table if exists doctors;
create temporary table doctors
(
    name varchar(50)
    ,occupation varchar(50)
    ,ordinal int
);
insert into doctors
    select 
        name
        ,occupation
        ,row_number() over (partition by occupation order by name) as ordinal
    from occupations
    where occupation = 'Doctor'
;
drop table if exists actors;
create temporary table actors
(
    name varchar(50)
    ,occupation varchar(50)
    ,ordinal int
);
insert into actors
    select 
        name
        ,occupation
        ,row_number() over (partition by occupation order by name) as ordinal
    from occupations
    where occupation = 'Actor'
;
drop table if exists professors;
create temporary table professors
(
    name varchar(50)
    ,occupation varchar(50)
    ,ordinal int
);
insert into professors
    select 
        name
        ,occupation
        ,row_number() over (partition by occupation order by name) as ordinal
    from occupations
    where occupation = 'Professor'
;
drop table if exists singers;
create temporary table singers
(
    name varchar(50)
    ,occupation varchar(50)
    ,ordinal int
);
insert into singers
    select 
        name
        ,occupation
        ,row_number() over (partition by occupation order by name) as ordinal
    from occupations
    where occupation = 'Singer'
;

-- upsert: update if not exists
drop table if exists results;
create temporary table results
(
    singer varchar(50)
    ,actor varchar(50)
    ,doctor varchar(50)
    ,professor varchar(50)
    ,ordinal int primary key
);
insert into results (singer, ordinal) 
    select name, ordinal from singers
on duplicate key update singer = name
;
insert into results (actor, ordinal) 
    select name, ordinal from actors
on duplicate key update actor = name
;
insert into results (doctor, ordinal) 
    select name, ordinal from doctors
on duplicate key update doctor = name
;
insert into results (professor, ordinal) 
    select name, ordinal from professors
on duplicate key update professor = name
;
select singer, actor, doctor, professor from results;

Ps。我不得不不同意先前的评论:这是关键。我们将行投影为列,这些行是职业和常规的投影。

答案 2 :(得分:0)

请看JSON服务(JSON_OBJECTAGG,JSON_OBJECT),可以使用盆地对象映射(Jackson)在Java中对其进行解析。

select xyz, JSON_OBJECTAGG( a, b) as pivit_point
from ... group by xyz;

答案 3 :(得分:0)

最好从结果开始并尝试映射到原始表。基本上,结果表的每一行应与原始表在同一组中。 CTE表分组和排名窗口功能按名称创建分组顺序。

with grouping as (
select
Name,
job_title,
rank() over (partition by job_title order by name) as rnk
from jobs
)

select
group_concat(if(g.job_title = 'Fireman', g.Name, NULL)) as 'Fireman',
group_concat(if(g.job_title = 'Driver',g.Name, NULL)) as 'Driver',
group_concat(if(g.job_title = 'Analyst', g.Name, NULL)) as 'Analyst',
group_concat(if(g.job_title = 'Research Manager', g.Name, NULL)) as 'Research Manager'
from grouping g
group by g.rnk
order by g.rnk

答案 4 :(得分:-1)

您发布的所需输出数据不是轮转数据的示例,因为同一行中的值彼此没有关系,听起来您只想要每个人的紧凑表示 - 细胞基础。这使它成为视图级别的问题,不应该在SQL中执行,而应该在视图级别执行(可能是PHP网页,因为您正在使用MySQL)。

您的输出数据是面向列的,而不是面向行的,但HTML表(以及其他平台的大多数数据网格组件,如WinForms,Java和WPF)都是面向行的,因此您需要考虑如何操作它

假设您的目标是HTML,并考虑了所需的面向行和列的转换,请尝试此(伪代码)

define type DBResultRow {
    id: int,
    name: string,
    job_title: string
}

let rows : List<DBResultRow> = // get rows from your view, no changes to SQL required

let jobTitles : List<String>
let jobTitleMap : Map<String,Int32>
let outputTable : List<List<String>>

foreach( person: DBResultRow in rows )
{
    let columnIdx = jobTitleMap[ person.job_title ];
    if( !columnIdx )
    {
        jobTitles.Add( person.job_title );
        columnIdx = jobTitles.Count - 1;
        jobTitleMap[ person.job_title, columnIdx ];
    }

    outputTable[ columnIdx ].Add( person.name );
}

let longestColumnLength = outputTable.Select( col => col.Count ).Max();

然后渲染为HTML:

<table>
    <thead>
        <tr>
foreach( jobTitle: String in jobTitles )
{
            <th><%= jobTitle #></th>
}
        </tr>
    </thead>
    <tbody>
for( row = 0; row < longestColumnLength; row++ )
{
        <tr>
    for( col = 0; col < jobTitles.Count; col++ )
    {
        if( row > outputTable[ col ].Count )
        {
            <td></td>
        }
        else
        {
            <td><%= outputTable[ col ][ row ] %></td>
        }
    }
        </tr>
}
    </tbody>
</table>