2010-01-20 7 views
36

У меня есть интересная загадка, которая, я считаю, может быть решена в чисто SQL. У меня есть таблицы, аналогичные приведенным ниже:SQL Transpose Rows as Columns

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

Итак, вы говорите, что мне нужно построить динамический запрос, основанный на количестве вопросов, которые у меня есть? Думаю, я мог бы это сделать, но я надеялся на более простое решение. –

+0

@Topher: Oracle и SQL Server имеют 'PIVOT' и' UNPIVOT', но если вы проверите тэг pivot, вы увидите, что динамические запросы являются общими даже с функцией. –

+1

Спасибо за ответ. Похоже, это будет проще всего реализовать, даже если мне нужно сгенерировать запрос во время выполнения. –

0

Существует пример этого в contrib/tablefunc/.

+1

Умм, где 'вно/tablefunc'? Вы говорите о директории на сервере doc? –

+0

Он находится в этом каталоге в исходном дереве, или вы можете найти двоичный пакет 'postgresql-contrib', который вам нужно установить, который его содержит. –

+1

Сокращенный для низкого качества ответа. Вы не представили никакого контекста, не предоставили раздел исходного материала (в случае его изменения) и не приложили никаких усилий, чтобы связать его с вопросом. См. Это для получения более качественных ответов http://stackoverflow.com/help/how-to-answer –

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

Очень приятно! Спасибо, что поделились и создали его с открытым исходным кодом! Хотелось бы увидеть некоторые ориентиры относительно его производительности (особенно здесь, когда новые поисковики захотят быть уверенными в своих возможностях). –

+0

как я могу избавиться от цитаты? в именах столбцов – Diego

+0

Отлично! спасибо. +1 – John