SQL / Postgres-根据组中的行位置将每N行折叠为1

时间:2019-05-14 15:15:25

标签: sql postgresql

我从Postgres表中获得一组有序结果,其中每4行的组代表一组相关数据。我想进一步处理这组结果,以便将每4行的组折叠为具有别名列名的1行,其中每列的值基于该行在组中的位置-我很接近,但是我可以不太正确的查询(我也不相信我正在以最佳方式解决这个问题)。这是场景:

我正在收集调查结果-每个调查都有4个问题,但是每个答案都存储在数据库的单独行中。但是,它们通过提交event_id相互关联,并且保证结果以固定顺序返回。一组survey_results如下所示:

  event_id   |    answer
----------------------------
     a       |     10
     a       |     foo
     a       |     9
     a       |     bar
     b       |     2
     b       |     baz
     b       |     4
     b       |     zip

我想做的就是查询该结果,以便最终输出在自己的行上显示每组4个结果,并带有别名列名。

event_id  |  score_1  |  reason_1  |  score_2  |  reason_2
----------------------------------------------------------
    a     |   10      |    foo     |     9     |    bar
    b     |   2       |    baz     |     4     |    zip

我能得到的最接近的是

SELECT survey_answers.event_id,
    (SELECT survey_answers.answer FROM survey_answers FETCH NEXT 1 ROWS ONLY) AS score_1,
    (SELECT survey_answers.answer FROM survey_answers OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY) AS reason_1
    (SELECT survey_answers.answer FROM survey_answers OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY) AS score_2,
    (SELECT survey_answers.answer FROM survey_answers OFFSET 3 ROWS FETCH NEXT 1 ROWS ONLY) AS reason_2
FROM survey_answers
GROUP BY survey_answers.event_id

但是,可以理解的是,这返回正确的行数,但是具有相同的值(event_id以外):

event_id  |  score_1  |  reason_1  |  score_2  |  reason_2
----------------------------------------------------------
    a     |   10      |    foo     |     9     |    bar
    b     |   10      |    foo     |     9     |    bar

如何构造查询以使其每4行(或更准确地说,在每个唯一OFFSET组中)应用FETCH / event_id行为?

2 个答案:

答案 0 :(得分:2)

demo: db<>fiddle

首先,这看起来是一个非常糟糕的设计:

  1. 没有保证的订单!数据库以随机顺序存储数据,并以随机顺序调用它们。您确实需要一个订单栏。在这种小情况下,这可能会导致意外。

  2. 您应该生成两列,一列用于得分,一列用于原因。混合类型不是一个好主意。

不过,对于这个简单而简短的示例,这可能是一个解决方案(请记住,不建议将其用于生产型表):

WITH data AS (
    SELECT 
        *,
        row_number() OVER (PARTITION BY event_id)    -- 1
    FROM 
        survey_results
)
SELECT
    event_id,
    MAX(CASE WHEN row_number = 1 THEN answer END) AS score_1,    -- 2
    MAX(CASE WHEN row_number = 2 THEN answer END) AS reason_1,
    MAX(CASE WHEN row_number = 3 THEN answer END) AS score_2,
    MAX(CASE WHEN row_number = 4 THEN answer END) AS reason_2
FROM
    data
GROUP BY event_id
  1. row_number()window function为每个event_id添加一个行数。在这种情况下,范围是1到4。这可以用来标识answer的类型(请参阅小提琴中的中间步骤)。在生产代码中,您应该使用一些订单列来确保订单。然后,窗口函数看起来像PARTITION BY event_id ORDER BY order_column
  2. 这是event_id和类型ID(row_number)的简单枢纽,它完全符合您的期望

答案 1 :(得分:2)

您需要一列来指定顺序。在您的情况下,可能应该是serial列,并保证每次插入都会增加。我将这样的列称为survey_result_id

使用这样的列,您可以执行以下操作:

select event_id,
       max(case when seqnum = 1 then answer end) as score_1,
       max(case when seqnum = 2 then answer end) as reason_1,
       max(case when seqnum = 3 then answer end) as score_2,
       max(case when seqnum = 4 then answer end) as reason_2
from (select sr.*,
             row_number() over (partition by event_id order by survey_result_id) as seqnum
      from survey_results sr
     ) sr
group by event_id;

如果没有这样的列,则无法可靠地执行所需的操作,因为SQL表表示无序集。