我有一个关于如何在 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 查询指南!
有 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)
我在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;
诗。我不得不不同意之前的评论:这是一个支点。我们将行投影到列中,行是职业和序数的投影。
不使用表视图,您可以使用SQL CTE(通用表表达式)获取表并通过ROW_NUMBER()对列进行分区。这就是我尝试拿到桌子的方法。
create table Employees( ID int PRIMARY KEY, Name varchar(50), Job_title varchar(50) )
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');
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;
您发布的所需输出数据是不是透视数据的示例,因为同一行中的值彼此没有关系,听起来您只想在每个单元格的基础上紧凑地表示每个人。这使得它成为视图级别的关注点,不应该在 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>
看看JSON服务
(JSON_OBJECTAGG,JSON_OBJECT)
,它可以用盆地对象映射(Jackson)在java中解析。
select xyz, JSON_OBJECTAGG( a, b) as pivit_point
from ... group by xyz;
最好从结果开始,尝试映射到原始表。基本上结果表的每一行都应该位于原始表中的同一组中。 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