将 VARCHAR 数据排列到 SQL 中的列中

Arranging VARCHAR data into Columns in SQL

我的数据在 SQL table 中,格式如下(每个 user_id 我有大约 20 个不同的答案):

USER_ID     ANSWER
--------------------------
1           CAR
1           10-20 miles 
1           SALES 
2           TRAIN
2           0-10 miles
2           TEACHER

我想在 PostgreSQL 中创建一个视图,其中根据用户 ID 显示所有数据

USER_ID     ANSWER1     ANSWER2        ANSWER3
-----------------------------------------------
1           CAR         10-20 miles    SALES
2           TRAIN       0-10 miles     TEACHER

谢谢!

这是一个很好的非关系数据的例子(语义取决于行号),尽管数据保存在关系数据库中。您不能从关系数据库内部的非关系中创建关系数据。没有任何机制可以安全地进行这种转换。从理论上讲,如果您的数据的物理顺序与图片中的相同,那么您可以编写查询(但您不应该使用 ORDER BY 子句,因为它可以更改顺序,因为您的数据不包含必要的数据正确排序):

您需要函数:

CREATE OR REPLACE FUNCTION public.x_transformed()
 RETURNS TABLE(user_id integer, rowno integer, answer character varying)
 LANGUAGE plpgsql
AS $function$
declare r record; 
prev_user_id int;
begin
  for r in select * from x
  loop
    if user_id is distinct from r.user_id then
      rowno := 1;
    else
      rowno := rowno + 1;
    end if;
    user_id = r.user_id;
    answer = r.answer;
    return next;
  end loop;
end;
$function$

此函数可以将缺失的订单信息添加到关系中。这是一个非常丑陋的解决方案,但您的输入数据格式非常不满意,并且不可能有任何干净的解决方案(基于关系代数):

postgres=# select * from x_transformed();
┌─────────┬───────┬─────────────┐
│ user_id │ rowno │   answer    │
╞═════════╪═══════╪═════════════╡
│       1 │     1 │ CAR         │
│       1 │     2 │ 10-20 miles │
│       1 │     3 │ SALES       │
│       2 │     1 │ TRAIN       │
│       2 │     2 │ 0-10 miles  │
│       2 │     3 │ TEACHER     │
└─────────┴───────┴─────────────┘
(6 rows)

现在,转换为请求的格式很容易(搜索旋转):

select user_id,
       max(answer) filter (where rowno = 1) as answer1,
       max(answer) filter (where rowno = 2) as answer2,
       max(answer) filter (where rowno = 3) as answer3
 from x_transformed() group by user_id;
┌─────────┬─────────┬─────────────┬─────────┐
│ user_id │ answer1 │   answer2   │ answer3 │
╞═════════╪═════════╪═════════════╪═════════╡
│       2 │ TRAIN   │ 0-10 miles  │ TEACHER │
│       1 │ CAR     │ 10-20 miles │ SALES   │
└─────────┴─────────┴─────────────┴─────────┘
(2 rows)

主要问题出在输入数据的格式上。它对于文件和应用程序处理来说已经足够好了,但对于关系数据库中的存储(和处理)来说却非常糟糕。关系(或 table)是堆(不是文件)。

通过了解 Postgres 的内部结构,您可以确保预期的顺序,尽管您使用一些关系操作。您可以使用内部隐式列 ctidctid 是行的唯一地址。这不适用于其他数据库,也不适用于较旧的 Postgres 版本:

select user_id,
       max(answer) filter (where rowno = 1) as answer1,
       max(answer) filter (where rowno = 2) as answer2,
       max(answer) filter (where rowno = 3) as answer3
  from (select user_id,
               answer,
               row_number() over (partition by user_id
                                  order by ctid) as rowno
          from x) s
 group by user_id
 order by user_id;
┌─────────┬─────────┬─────────────┬─────────┐
│ user_id │ answer1 │   answer2   │ answer3 │
╞═════════╪═════════╪═════════════╪═════════╡
│       1 │ CAR     │ 10-20 miles │ SALES   │
│       2 │ TRAIN   │ 0-10 miles  │ TEACHER │
└─────────┴─────────┴─────────────┴─────────┘
(2 rows)

fiddle 对于正则表达式:https://regex101.com/r/oUgYbj/1

这仅在假设您对 q1、q2、q3 的顺序始终相同的情况下才有效 我相信您的数据应该有一些字段来排序,以避免混淆,比如新列:Question_rank 然后它显示值 1 表示汽车和火车,2 表示英里,3 表示工作,即销售/教师。

这样我们就可以在 order by 子句中将 Question_rank 作为第二列

with data as(
select
user_id,
STRING_AGG(answer,',') as all_answers
from table
group by 1
Order by id
)
select
user_id,
REGEXP_MATCHES (all_answers,'^([^,])+') AS answer_1,
REGEXP_MATCHES (all_answers,'\d+(-)\d+\s+\w+') AS answer_2
REGEXP_MATCHES (all_answers,'[^,]*$') AS answer_3
from data