Подтвердить что ты не робот

Строки транспонирования 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: Я нашел некоторую информацию об использовании кросс-таблицы, которая, кажется, есть то, что я хочу, но я У меня проблемы с пониманием этого. Ссылки на лучшие статьи были бы с благодарностью!

4b9b3361

Ответ 1

Использование:

  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

Это стандартный сводный запрос, потому что вы "сворачиваете" данные из строк в столбчатые данные.

Ответ 2

Я реализовал действительно динамическую функцию для решения этой проблемы без необходимости жесткого кодирования какого-либо определенного класса ответов или использования внешних модулей/расширений. Он также обеспечивает полный контроль над упорядочением столбцов и поддерживает несколько столбцов ключей и классов/атрибутов.

Вы можете найти его здесь: 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

Ответ 3

Вы можете решить этот пример с помощью кросс-таблицы таким образом

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:

CREATE EXTENSION tablefunc;

Ответ 4

Я написал функцию для генерации динамического запроса. Он генерирует 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;

Ответ 5

Вот пример этого в contrib/tablefunc/.