【问题标题】:Arranging VARCHAR data into Columns in SQL在 SQL 中将 VARCHAR 数据排列到列中
【发布时间】:2021-10-26 18:16:48
【问题描述】:

我的数据在 SQL 表中,格式如下(每个 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

谢谢!

【问题讨论】:

  • 您的数据不包含订购信息,请务必将 answer1 与 answer2 与 answer3 区别开来。

标签: sql postgresql join pivot


【解决方案1】:

这是非关系数据的一个很好的例子(语义取决于行号),尽管数据保存在关系数据库中。您不能从关系数据库内部的非关系中生成关系数据。没有任何机制可以安全地进行这种转换。从理论上讲,如果您的数据的物理顺序与图片中的相同,那么您可以编写查询(但您不应该使用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)

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

了解 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)

【讨论】:

  • 另外 - 我认为 postgres 结果没有排序(例如,如果表被清理或发生其他内部内务处理,则隐式顺序可能会改变)所以即使采用这种聪明的方法,也不能保证 rowno 会是查询之间保持一致。
  • 当你只是读取而没有任何其他操作,并且没有任何其他并行读取,并且数据是从文件中导入而没有任何更新和删除,那么它可以工作,但可以肯定 - 它是非常脆弱。关系表不是文件,这是糟糕设计的一个很好的例子。
  • @PavelStehule 您能否解释一下您的代码如何确保车辆类型始终获得第 1 行,工作始终获得 3 和英里数 2。目前尚不清楚如何正常工作
  • 基本上他依赖于 postgres 在最初将行写入页面文件时存储的行顺序。有很多假设,但一般来说postgres应该一个接一个地插入它们,并按照它们写入的顺序读取它们。
  • 如果我们编辑/删除行,postgres 可能会清理并重写行,从而破坏排序。如果我们的查询很复杂(例如 group by),它可以重新排序隐式顺序。但是如果我们坚持简单的读取并且不接触数据,它应该保持与写入相同的顺序。正如我们都指出的那样,这是非常糟糕/脆弱的,应该添加一个列订购者。
【解决方案2】:

正则表达式的小提琴: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

【讨论】:

  • 不完全安全,因为GROUP BY可以改变行的顺序
  • 无关紧要的正则表达式每次都在寻找不同的模式,而不是总是专注于一个特定的序列:请参阅regex101.com/r/xvVrGz/1
  • 不幸的是,它是随机的 - group by 子句可以通过 user_id 进行重新散列或重新排序。对于小数据,它使用散列,因此没有理由在不同的旧版本中处理聚合。但是对于较大的数据集,postgres 可以使用基于排序的聚合 - 在这种情况下基于 user_id,但组内的顺序取决于快速排序处理。
  • 我相信可以添加该订单
  • order by 没有帮助 - 没有任何用于排序的列
猜你喜欢
  • 2019-12-19
  • 2014-02-23
  • 2022-09-26
  • 2019-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
相关资源
最近更新 更多