Сводные строки в столбцы на основе содержимого в Oracle 10g PL / SQL - PullRequest
2 голосов
/ 02 ноября 2009

В моей базе данных есть таблица, user_answers, в которой хранятся ответы пользователей на ряд вопросов со строками; user_id, question_id, answer_id и text_entry. Текст вопроса и текст ответа (если есть) хранятся в справочных таблицах. Существует три типа вопросов: вопросы с одним ответом, вопросы с несколькими ответами и вопросы с текстовым ответом. Таким образом, в таблице user_answers для одного пользователя могут быть следующие записи:

user_id    question_id    answer_id    text_entry
-------    -----------    ---------    ----------
  123          100          1010         (null)
  123          200          2010         (null)
  123          200          2030         (null)
  123          300          3000       "code 789"

Допустим, таблица questions_text имеет:

question_id         text
-----------    -------------
    100           "Gender"
    200         "Interests"
    300         "Your code"

и таблица answers_text имеет:

answer_id       text
---------    -----------
   1010       "Female"
   1020        "Male"
   2010       "Sports"
   2020      "Computers"
   2030       "Movies"
   3000        (null)

Я хочу извлечь данные в csv с одной строкой на user_id, показывающей ответы, что-то вроде этого:

User,Gender,Sports,Computers,Movies,Code
123,Female,1,0,1,code 789

Я знаю, как генерировать файл CSV через SQLPlus (у меня есть доступ к БД через SQLPlus только по независящим от меня причинам ...), но я не знаю, как создать оператор PL / SQL.

В PL / SQL я знаю, что могу сгенерировать стержень вопроса о поле, выполнив

SELECT
   user_id || ',' ||
   MIN(DECODE(question_id, '100', (SELECT text FROM answers_text where answer_id = answer_text.answer_id)))
FROM user_answers
GROUP BY user_id
ORDER BY user_id
;

(я не SQL-парень, так что это скопировано из интернета!)

Этот код (по крайней мере, насколько мне подсказывает мое тестирование) хорош для вопросов с одним ответом, но не будет работать с вопросами с несколькими ответами или вопросами типа ввода текста.

В Интернете я видел кое-что об использовании оператора case в PL / SQL, например:

MIN(CASE WHEN question_id = '200' AND answer_id = '2010' THEN '1' ELSE '0' END)

... но я не могу понять, как получить ответы в столбцах. И все вопросы, которые я могу найти, которые могут быть связаны, связаны с sql-сервером.

Есть ли способ сгенерировать желаемый результат из одного оператора PL / SQL? Желательно писать так, чтобы это не зависело от данных в таблицах, так как у нас есть ряд баз данных, на которых это может потребоваться.

Ответы [ 2 ]

1 голос
/ 05 ноября 2009

Запросы с неизвестным количеством столбцов в лучшем случае проблематичны. Вы действительно не будете знать, как будут выглядеть данные? Возможно, вы захотите посмотреть на этот ответ Ask Tom для пакета, который может помочь вам получить необходимые результаты.

1 голос
/ 05 ноября 2009

Чтобы выполнить то, что вы ищете (а не конкретизировать эти данные), я считаю, что вам понадобятся дополнительные поля в ваших таблицах. Например, вам нужно будет знать, какие вопросы являются «Один ответ», «Несколько ответов» и «Ввод текста», не обращая внимания на данные. Вам также необходимо знать, какие ответы возможны для ваших вопросов с несколькими ответами, без необходимости связывать данные. Оттуда вы можете просмотреть мета-информацию о каждой комбинации вопрос / ответ и создать собственный запрос, который вернет данные в нужном вам формате. Что-то вроде:

/* Create Tables with Data - Note 2 new columns added to questions_text */
create table user_answers
as
 select 123 user_id, 100 question_id, 1010 answer_id, null text_entry from dual
 union all
 select 123 user_id, 200 question_id, 2010 answer_id, null text_entry from dual
 union all
 select 123 user_id, 200 question_id, 2030 answer_id, null text_entry from dual
 union all
 select 123 user_id, 300 question_id, 3000 answer_id, 'code 789' text_entry from dual;

create table questions_text
as
 select 100 question_id, 'Gender' question_text, 'S' question_type, 1000 answer_set_id from dual
 union all
 select 200 question_id, 'Interests' question_text, 'M' question_type, 2000 answer_set_id from dual
 union all
 select 300 question_id, 'Your code' question_text, 'T' question_type, 3000 answer_set_id from dual;

create table answers_text
as
 select 1010 answer_id, 'Female' text, 1000 answer_set_id from dual
 union all
 select 1020 answer_id, 'Male' text, 1000 answer_set_id from dual
 union all
 select 2010 answer_id, 'Sports' text, 2000 answer_set_id from dual
 union all
 select 2020 answer_id, 'Computers' text, 2000 answer_set_id from dual
 union all
 select 2030 answer_id, 'Movies' text, 2000 answer_set_id from dual
 union all
 select 3000 answer_id, null text, 3000 answer_set_id from dual;


/* PL/SQL for creating SQL statement to return data in desired format */
declare
 v_sql VARCHAR2(32767);
begin
 v_sql := 'select ua.user_id "User",';
 FOR question IN (
  select question_id, question_text, question_type, answer_set_id
  from questions_text
 )
 LOOP
  IF question.question_type = 'M'
  THEN
   FOR answer IN (
    select answer_id, text
    from answers_text
    where answer_set_id = question.answer_set_id
   )
   LOOP
    v_sql := v_sql||chr(10)||'max(case when ua.question_id = '||question.question_id||' and ua.answer_id = '||answer.answer_id||' then 1 else 0 end) "'||answer.text||'",';
   END LOOP;
  ELSIF question.question_type = 'S'
  THEN
   v_sql := v_sql||chr(10)||'min(case when ua.question_id = '||question.question_id||' then at.text end) "'||question.question_text||'",';
  ELSIF question.question_type = 'T'
  THEN
   v_sql := v_sql||chr(10)||'min(case when ua.question_id = '||question.question_id||' then ua.text_entry end) "'||question.question_text||'",';
  END IF;
 END LOOP;
 v_sql := rtrim(v_sql,',');
 v_sql := v_sql||' from
 user_answers ua
 inner join questions_text qt
  on qt.question_id = ua.question_id
 inner join answers_text at
  on at.answer_id = ua.answer_id
 group by
  ua.user_id';
 -- replace dbms_output with code to write file
 dbms_output.put_line(v_sql);
END;
...