SQL将行转换为列
我有一个有趣的难题,我相信它可以用纯SQL解决。我有如下类似的表格:SQL将行转换为列,sql,postgresql,pivot,pivot-without-aggregate,Sql,Postgresql,Pivot,Pivot Without Aggregate,我有一个有趣的难题,我相信它可以用纯SQL解决。我有如下类似的表格: responses: user_id | question_id | body ---------------------------- 1 | 1 | Yes 2 | 1 | Yes 1 | 2 | Yes 2 | 2 | No 1 | 3 | No 2
responses:
user_id | question_id | body
----------------------------
1 | 1 | Yes
2 | 1 | Yes
1 | 2 | Yes
2 | 2 | No
1 | 3 | No
2 | 3 | No
questions:
id | body
-------------------------
1 | Do you like apples?
2 | Do you like oranges?
3 | Do you like carrots?
我想得到以下输出
user_id | Do you like apples? | Do you like oranges? | Do you like carrots?
---------------------------------------------------------------------------
1 | Yes | Yes | No
2 | Yes | No | No
我不知道会有多少问题,而且它们是动态的,所以我不能只为每个问题编写代码。我正在使用PostgreSQL,我相信这就是所谓的转置,但我似乎找不到任何东西可以说明在SQL中实现这一点的标准方法。我记得在大学的数据库课上做过这个,但那是在MySQL中,我真的不记得我们是怎么做的
我假设它将是连接和GROUPBY
语句的组合,但我甚至不知道如何开始
有人知道怎么做吗?非常感谢
编辑1:我找到了一些关于使用a的信息,这似乎是我想要的,但我很难理解它。链接到更好的文章将不胜感激 在
contrib/tablefunc/
中有这样一个例子使用:
SELECT r.user_id,
MAX(CASE WHEN r.question_id = 1 THEN r.body ELSE NULL END) AS "Do you like apples?",
MAX(CASE WHEN r.question_id = 2 THEN r.body ELSE NULL END) AS "Do you like oranges?",
MAX(CASE WHEN r.question_id = 3 THEN r.body ELSE NULL END) AS "Do you like carrots?"
FROM RESPONSES r
JOIN QUESTIONS q ON q.id = r.question_id
GROUP BY r.user_id
这是一个标准的数据透视查询,因为您正在将数据从行“透视”到列数据。您可以用函数以这种方式解决此示例
drop table if exists responses;
create table responses (
user_id integer,
question_id integer,
body text
);
drop table if exists questions;
create table questions (
id integer,
body text
);
insert into responses values (1,1,'Yes'), (2,1,'Yes'), (1,2,'Yes'), (2,2,'No'), (1,3,'No'), (2,3,'No');
insert into questions values (1, 'Do you like apples?'), (2, 'Do you like oranges?'), (3, 'Do you like carrots?');
select * from crosstab('select responses.user_id, questions.body, responses.body from responses, questions where questions.id = responses.question_id order by user_id') as ct(userid integer, "Do you like apples?" text, "Do you like oranges?" text, "Do you like carrots?" text);
首先,必须安装tablefunc扩展。从9.1版开始,您可以使用创建扩展:
CREATE EXTENSION tablefunc;
我编写了一个函数来生成动态查询。 它为交叉表生成sql并创建一个视图(如果它存在,则首先删除它)。 然后可以从视图中选择以获得结果 以下是函数:
CREATE OR REPLACE FUNCTION public.c_crosstab (
eavsql_inarg varchar,
resview varchar,
rowid varchar,
colid varchar,
val varchar,
agr varchar
)
RETURNS void AS
$body$
DECLARE
casesql varchar;
dynsql varchar;
r record;
BEGIN
dynsql='';
for r in
select * from pg_views where lower(viewname) = lower(resview)
loop
execute 'DROP VIEW ' || resview;
end loop;
casesql='SELECT DISTINCT ' || colid || ' AS v from (' || eavsql_inarg || ') eav ORDER BY ' || colid;
FOR r IN EXECUTE casesql Loop
dynsql = dynsql || ', ' || agr || '(CASE WHEN ' || colid || '=''' || r.v || ''' THEN ' || val || ' ELSE NULL END) AS ' || agr || '_' || r.v;
END LOOP;
dynsql = 'CREATE VIEW ' || resview || ' AS SELECT ' || rowid || dynsql || ' from (' || eavsql_inarg || ') eav GROUP BY ' || rowid;
RAISE NOTICE 'dynsql %1', dynsql;
EXECUTE dynsql;
END
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;
下面是我如何使用它:
SELECT c_crosstab('query_txt', 'view_name', 'entity_column_name', 'attribute_column_name', 'value_column_name', 'first');
例如:
跑步时:
SELECT c_crosstab('Select * from table', 'ct_view', 'usr_id', 'question_id', 'response_value', 'first');
比:
我实现了一个真正的动态函数来处理这个问题,而不必硬编码任何特定的答案类或使用外部模块/扩展。它还可以完全控制列顺序,并支持多个键和类/属性列 你可以在这里找到它: 解决此特定问题的示例:
begin;
create temporary table responses (
user_id integer,
question_id integer,
body text
) on commit drop;
create temporary table questions (
id integer,
body text
) on commit drop;
insert into responses values (1,1,'Yes'), (2,1,'Yes'), (1,2,'Yes'), (2,2,'No'), (1,3,'No'), (2,3,'No');
insert into questions values (1, 'Do you like apples?'), (2, 'Do you like oranges?'), (3, 'Do you like carrots?');
select colpivot('_output', $$
select r.user_id, q.body q, r.body a from responses r
join questions q on q.id = r.question_id
$$, array['user_id'], array['q'], '#.a', null);
select * from _output;
rollback;
这将产生:
user_id | 'Do you like apples?' | 'Do you like carrots?' | 'Do you like oranges?'
---------+-----------------------+------------------------+------------------------
1 | Yes | No | Yes
2 | Yes | No | No
嗯,
contrib/tablefunc
在哪里?你说的是doc服务器上的目录吗?它在源代码树中的那个目录中,或者你可能会发现需要安装的postgresql contrib
二进制软件包中包含它。对低质量的答案投了反对票。您没有提供任何上下文,没有提供源材料的一部分(以防其发生变化),也没有努力将其与问题联系起来。参考这篇文章获得更好的答案,你是说我必须根据我的问题数量构建一个动态查询?我想我可以这样做,但我希望有一个更简单的解决方案。@Topher:Oracle&SQL Server有PIVOT
和UNPIVOT
,但是如果您检查PIVOT标记,您会发现动态查询即使在函数中也很常见。谢谢您的回答。看起来这将是最容易实现的,即使我必须在运行时生成查询。此查询在PostgreSQL中不起作用。单引号必须是双引号。如果有动态行数(不是3行),该怎么办。那你怎么转置呢?很好!感谢您的分享和开源!我希望看到一些关于它性能的基准测试(尤其是在这里,因为新的搜索者希望对它的功能有信心)。我如何才能摆脱这个引用?在列名中
user_id | 'Do you like apples?' | 'Do you like carrots?' | 'Do you like oranges?'
---------+-----------------------+------------------------+------------------------
1 | Yes | No | Yes
2 | Yes | No | No