根据 Oracle 10g PL/SQL 中的内容将行转为列

Posted

技术标签:

【中文标题】根据 Oracle 10g PL/SQL 中的内容将行转为列【英文标题】:Pivot rows to columns based on content in Oracle 10g PL/SQL 【发布时间】:2009-11-02 13:59:52 【问题描述】:

我的数据库中有一个表user_answers,它存储用户对一系列问题的回答,并带有行; user_idquestion_idanswer_idtext_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

我知道如何通过 SQLPlus 生成 CSV 文件(由于我无法控制的原因,我只能通过 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 人,所以这是从互联网上复制的!)

此代码(至少就我的测试而言)适用于单答案问题,但不适用于多答案或文本输入类型的问题。

我在网上看到一些关于在 PL/SQL 中使用 case 语句的内容,如下所示:

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

...但我不知道如何将答案放入列中。我能找到的所有可能相关的 SO 问题都是特定于 sql-server 的。

有没有办法从单个 PL/SQL 语句生成我想要的输出?最好以不依赖于表中数据的方式编写,因为我们有许多可能需要在其上运行的数据库。

【问题讨论】:

【参考方案1】:

为了完成您正在寻找的内容(而不是特定于这些数据),我相信您需要在表格中添加一些额外的字段。例如,您需要知道哪些问题是单答案、多答案和文本输入,而无需查看数据。您还需要知道哪些答案可能适用于您的多选题,而无需链接数据。从那里,您可以遍历有关每个问题/答案组合的元信息,并为自己构建一个要运行的查询,该查询将以您想要的格式返回数据。比如:

/* 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;

【讨论】:

感谢 cmartin2。我最终使用了更依赖于这种情况的特定代码的东西。从长远来看,我计划根据您的回答使用一些东西——您拥有的附加信息在问题表中或可以构建(在 answer_id_set 的情况下,它是 question_id + 10x0,其中每个问题的 x 递增,即 1010, 1020等)【参考方案2】:

列数未知的查询充其量是有问题的。你真的不知道数据会是什么样子吗?您可能想查看此Ask Tom 响应以获取可能有助于获得所需结果的包。

【讨论】:

以上是关于根据 Oracle 10g PL/SQL 中的内容将行转为列的主要内容,如果未能解决你的问题,请参考以下文章

PL/SQL Oracle 10g 询问用户输入(替代变量除外)

使用 PL/SQL 过程在 oracle 10g 中转储表

如何在 java 中使用 jdbc 为 oracle 10g 执行 log miner PL/SQL 查询

如何从 Oracle 10G PL/SQL 函数和过程中查找所有表引用? [复制]

如何一键提示两个命令? pl/sql oracle 10g 表单生成器

如何使用触发器在同一张表中插入新行(Oracle PL/SQL 10G)?