如何在 MySQL 中透视表

问题描述 投票:0回答:7

我有一个关于如何在 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 查询指南!

mysql sql pivot-table
7个回答
3
投票

有 3 件事需要考虑 1) 如何动态生成一堆 max(case when 2) 分配一些东西来对 case when's by 进行分组 - 在这种情况下,我使用变量生成行号 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
投票

我在HackerRank上也遇到了这个问题。虽然我认为

group_concat
答案非常好,并且通常用于早期版本的 MySql 的此类枢轴情况,但我发现
concat
group_concat
可能难以阅读和理解。

如果您的 MySql 版本支持窗口函数,那么您可以使用临时表解决此问题,因为 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;

诗。我不得不不同意之前的评论:这是一个支点。我们将行投影到列中,行是职业和序数的投影。


0
投票

不使用表视图,您可以使用SQL CTE(通用表表达式)获取表并通过ROW_NUMBER()对列进行分区。这就是我尝试拿到桌子的方法。

  1. 创建员工表 - 设置环境
create table Employees(
    ID int PRIMARY KEY,
    Name varchar(50),
    Job_title varchar(50)
)
  1. 将数据插入员工表 - 设置环境
Insert Into Employees values(1, 'Sam', 'Fireman');
Insert Into Employees values(2, 'Tomas', 'Driver');
Insert Into Employees values(3, 'Peter', 'Fireman');
Insert Into Employees values(4, 'Lisa', 'Analyst');
Insert Into Employees values(5, 'Marcus', 'Postman');
Insert Into Employees values(6, 'Stephan', 'Analyst');
Insert Into Employees values(7, 'Mary', 'Research Manager');
Insert Into Employees values(8, 'Albert', 'Analyst');
Insert Into Employees values(9, 'Chen', 'Driver');
  1. 最终查询 - 从表中获取不同的角色并将其分区到单独的列中。
WITH ranked_data AS (
    SELECT
        Name,
        job_title,
        ROW_NUMBER() OVER (PARTITION BY job_title ORDER BY ID) AS row_num
    FROM
        Employees
)
SELECT
    MAX(CASE WHEN job_title = 'Fireman' THEN Name END) AS Fireman,
    MAX(CASE WHEN job_title = 'Driver' THEN Name END) AS Driver,
    MAX(CASE WHEN job_title = 'Analyst' THEN Name END) AS Analyst,
    MAX(CASE WHEN job_title = 'Postman' THEN Name END) AS Postman,
    MAX(CASE WHEN job_title = 'Research Manager' THEN Name END) AS `Research Manager`
FROM
    ranked_data
GROUP BY
    row_num
ORDER BY
    row_num;

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

-1
投票

看看JSON服务

(JSON_OBJECTAGG,JSON_OBJECT)
,它可以用盆地对象映射(Jackson)在java中解析。

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

-1
投票

最好从结果开始,尝试映射到原始表。基本上结果表的每一行都应该位于原始表中的同一组中。 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

-1
投票

如果您想从任何 MySQL 数据库轻松创建数据透视表(也称为交叉表),其中任何表的原始数据都转换为动态列,我推荐智能数据透视表。该工具使用非常易于使用的向导式界面来生成数据透视表,您稍后可以将其导出到 MS Excel。

智能数据透视表是一个动态PHP报告工具,这意味着每当您的数据库更新时它都会自动更新您的数据透视表。

从 MySQL DB 生成数据透视表

© www.soinside.com 2019 - 2024. All rights reserved.