2010-01-20 106 views
36

我有一个有趣的难题,我相信可以在纯SQL中解决。我有类似的表如下:SQL将行转换为列

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中,我真的不记得我们是如何做到的。

我假设它将是一个连接和一个GROUP BY声明的组合,但我甚至不知道如何开始。

任何人都知道如何做到这一点?非常感谢!

编辑1:我发现了一些关于使用crosstab的信息,这似乎是我想要的,但我无法理解它。更好的文章链接将不胜感激!

回答

46

用途:

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 

这是一个标准的支点查询,因为你是从行到列数据“旋转”的数据。

+0

所以你说我必须建立一个动态查询基于我有多少个问题?我想我可以做到这一点,但我希望有一个更简单的解决方案。 – 2010-01-20 05:28:31

+0

@Topher:Oracle和SQL Server有'PIVOT'和'UNPIVOT',但是如果你检查pivot标签,你会发现动态查询在使用这个函数时是很常见的。 – 2010-01-20 05:33:35

+1

感谢您的回答。看起来这将是最容易实现的,即使我必须在运行时生成查询。 – 2010-01-20 05:40:47

0

contrib/tablefunc/中有一个这样的例子。

+1

嗯,这里是'的contrib/tablefunc'?你在谈论文档服务器上的目录吗? – 2010-01-20 05:24:12

+0

它位于源代码树中的目录中,或者您可能会发现需要安装的包含它的'postgresql-contrib'二进制包。 – 2010-01-20 07:13:55

+1

Downvoted为质量差的答案。您没有提供任何背景信息,也没有提供源材料的一部分(如果它发生变化),也没有做出任何努力将其与问题联系起来。 参考这个更好的答案http://stackoverflow.com/help/how-to-answer – 2015-12-05 02:52:50

6

可以解决与crosstab功能这个例子这样

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

我写了一个函数来生成动态查询。 它为交叉表生成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'); 

比:

Select * from ct_view; 
10

我实现了一个真正的动态函数来处理这个问题,而不必硬编码任何特定的答案类或使用外部模块/扩展。它还完全控制列排序并支持多个键和类/属性列。

你可以在这里找到:https://github.com/jumpstarter-io/colpivot

例,解决了这方面的问题:

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

非常好!感谢分享并使其成为开源软件!希望看到一些关于它的性能的基准(特别是在这里,因为新的搜索者会希望对它的能力有信心)。 – 2015-10-19 17:12:59

+0

我该如何摆脱报价?在列名 – Diego 2017-06-23 04:20:29

+0

太好了,谢谢。 +1 – John 2017-08-02 15:42:45