2010-01-20 272 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