红移。将逗号分隔的值转换为行

时间:2014-08-04 05:43:54

标签: amazon-redshift

我想知道如何在Redshift中将逗号分隔的值转换为行。我担心我自己的解决方案并不是最优的。请指教。我有一个表与其中一个具有逗号分隔值的列。例如:

我有:

user_id|user_name|user_action
-----------------------------
1      | Shone   | start,stop,cancell...

我想看看

user_id|user_name|parsed_action 
------------------------------- 
1      | Shone   | start        
1      | Shone   | stop         
1      | Shone   | cancell      
....

9 个答案:

答案 0 :(得分:25)

对现有答案的略微改进是使用第二个“数字”表来枚举所有可能的列表长度,然后使用cross join使查询更紧凑。

Redshift没有一种简单的方法来创建我所知道的数字表,但是我们可以使用https://www.periscope.io/blog/generate-series-in-redshift-and-mysql.html中的一些hack来创建一个使用行号的数据库。

具体来说,如果我们假设cmd_logs中的行数大于user_action列中的最大逗号数,我们可以通过计算行来创建数字表。首先,我们假设user_action列中最多有99个逗号:

select 
  (row_number() over (order by true))::int as n
into numbers
from cmd_logs
limit 100;

如果我们想要获得幻想,我们可以计算cmd_logs表中逗号的数量,以便在numbers中创建更精确的行集:

select
  n::int
into numbers
from
  (select 
      row_number() over (order by true) as n
   from cmd_logs)
cross join
  (select 
      max(regexp_count(user_action, '[,]')) as max_num 
   from cmd_logs)
where
  n <= max_num + 1;

一旦有numbers表,我们就可以:

select
  user_id, 
  user_name, 
  split_part(user_action,',',n) as parsed_action 
from
  cmd_logs
cross join
  numbers
where
  split_part(user_action,',',n) is not null
  and split_part(user_action,',',n) != '';

答案 1 :(得分:2)

您可以使用以下查询获得预期结果。我正在使用&#34; UNION ALL&#34;将列转换为行。

select user_id, user_name, split_part(user_action,',',1) as parsed_action from cmd_logs
union all
select user_id, user_name, split_part(user_action,',',2) as parsed_action from cmd_logs
union all
select user_id, user_name, split_part(user_action,',',3) as parsed_action from cmd_logs

答案 2 :(得分:1)

另一个想法是首先将您的CSV字符串转换为JSON,然后按照以下行将JSON提取转换为:

... '["' || replace( user_action, '.', '", "' ) || '"]' AS replaced

... JSON_EXTRACT_ARRAY_ELEMENT_TEXT(replaced, numbers.i) AS parsed_action

其中&#34;数字&#34;是第一个答案的表格。这种方法的优点是能够使用内置的JSON功能。

答案 3 :(得分:0)

这是我同样可怕的答案。

我有一个users表,然后是一个events表,其中一列只是在所述事件中以逗号分隔的用户字符串。例如

event_id | user_ids
1        | 5,18,25,99,105

在这种情况下,我使用LIKE和通配符函数构建一个表示每个事件用户边缘的新表。

SELECT e.event_id, u.id as user_id
FROM events e
LEFT JOIN users u ON e.user_ids like '%' || u.id || '%'

它并不漂亮,但我把它放在WITH条款中,这样我就不必每次查询多次运行它。我可能只是每晚构建一个ETL来创建该表。

此外,只有当第二个表 每个唯一可能性有一行时,这才有效。如果没有,您可以LISTAGG获取包含所有值的单个单元格,将其导出为CSV并重新上载 作为表格以提供帮助。

像我说的那样:一个糟糕的,不好的解决方案。

答案 4 :(得分:0)

参加派对的时间已晚,但我有一些工作(虽然虽然很慢)

with nums as (select n::int n
from
  (select 
      row_number() over (order by true) as n
   from table_with_enough_rows_to_cover_range)
cross join
  (select 
      max(json_array_length(json_column)) as max_num 
   from table_with_json_column )
where
  n <= max_num + 1)
select *, json_extract_array_element_text(json_column,nums.n-1) parsed_json
from  nums, table_with_json_column
where json_extract_array_element_text(json_column,nums.n-1) != ''
and nums.n <= json_array_length(json_column) 

感谢answer by Bob Baxley获取灵感

答案 5 :(得分:0)

仅针对https://stackoverflow.com/a/31998832/1265306

以上的答案进行改进

使用以下SQL生成数字表 https://discourse.looker.com/t/generating-a-numbers-table-in-mysql-and-redshift/482

SELECT 
  p0.n 
  + p1.n*2 
  + p2.n * POWER(2,2) 
  + p3.n * POWER(2,3)
  + p4.n * POWER(2,4)
  + p5.n * POWER(2,5)
  + p6.n * POWER(2,6)
  + p7.n * POWER(2,7) 
  as number  
INTO numbers
FROM  
  (SELECT 0 as n UNION SELECT 1) p0,  
  (SELECT 0 as n UNION SELECT 1) p1,  
  (SELECT 0 as n UNION SELECT 1) p2, 
  (SELECT 0 as n UNION SELECT 1) p3,
  (SELECT 0 as n UNION SELECT 1) p4,
  (SELECT 0 as n UNION SELECT 1) p5,
  (SELECT 0 as n UNION SELECT 1) p6,
  (SELECT 0 as n UNION SELECT 1) p7
ORDER BY 1
LIMIT 100

“ORDER BY”仅在您想要粘贴它而没有INTO子句并且看到结果的情况下

答案 6 :(得分:0)

创建一个存储过程,该过程将动态分析字符串并填充temp表,从temp表中选择。

这是魔术代码:-

  CREATE OR REPLACE PROCEDURE public.sp_string_split( "string" character varying )
AS $$
DECLARE 
  cnt INTEGER := 1;
    no_of_parts INTEGER := (select REGEXP_COUNT ( string , ','  ));
    sql VARCHAR(MAX) := '';
    item character varying := '';
BEGIN

  -- Create table
  sql := 'CREATE TEMPORARY TABLE IF NOT EXISTS split_table (part VARCHAR(255)) ';
  RAISE NOTICE 'executing sql %', sql ;
  EXECUTE sql;

  <<simple_loop_exit_continue>>
  LOOP
    item = (select split_part("string",',',cnt)); 
    RAISE NOTICE 'item %', item ;
    sql := 'INSERT INTO split_table SELECT '''||item||''' ';
    EXECUTE sql;
    cnt = cnt + 1;
    EXIT simple_loop_exit_continue WHEN (cnt >= no_of_parts + 2);
  END LOOP;

END ;
$$ LANGUAGE plpgsql;


用法示例:-

  call public.sp_string_split('john,smith,jones');
select *
from split_table

答案 7 :(得分:0)

如果您知道 user_action 列中没有很多操作,您可以使用带有 union all 的递归子查询,从而避免使用 aux numbers 表。

但是它需要您知道每个用户的操作次数,要么调整初始表,要么为其创建视图或临时表。

数据准备

假设你有这样一个表格:

create temporary table actions 
(
    user_id varchar,
    user_name varchar,
    user_action varchar
);

我将在其中插入一些值:

insert into actions
values (1, 'Shone', 'start,stop,cancel'),
       (2, 'Gregory', 'find,diagnose,taunt'),
       (3, 'Robot', 'kill,destroy');

这是一个带有临时计数的附加表

create temporary table actions_with_counts 
(
    id varchar,
    name varchar,
    num_actions integer,
    actions varchar
);

insert into actions_with_counts (
    select user_id,
           user_name,
           regexp_count(user_action, ',') + 1 as num_actions,
           user_action
    from actions
);

这将是我们的“输入表”,它看起来和您预期的一样

select * from actions_with_counts;
<头>
id 名称 num_actions 动作
2 格雷戈里 3 查找、诊断、嘲讽
3 机器人 2 杀死,摧毁
1 闪耀 3 开始、停止、取消

同样,您可以调整初始表,从而跳过将计数添加为单独的表。

用于扁平化操作的子查询

这是取消嵌套的查询:

with recursive tmp (user_id, user_name, idx, user_action) as 
(
    select id,
           name,
           1 as idx,
           split_part(actions, ',', 1) as user_action
    from actions_with_counts

    union all

    select user_id,
           user_name,
           idx + 1 as idx,
           split_part(actions, ',', idx + 1)
    from actions_with_counts
         join tmp on actions_with_counts.id = tmp.user_id
    where idx < num_actions
)
select user_id, user_name, user_action as parsed_action
from tmp
order by user_id;

这将为每个操作创建一个新行,输出如下所示:

<头>
user_id user_name parsed_action
1 闪耀 开始
1 闪耀 停止
1 闪耀 取消
2 格雷戈里 查找
2 格雷戈里 诊断
2 格雷戈里 嘲讽
3 机器人 kill
3 机器人 销毁

答案 8 :(得分:-3)

您可以尝试复制命令将文件复制到红移表

copy table_name from 's3://mybucket/myfolder/my.csv' CREDENTIALS 'aws_access_key_id=my_aws_acc_key;aws_secret_access_key=my_aws_sec_key' delimiter ','

您可以使用分隔符','选项。

有关复制命令选项的更多详细信息,请访问此页面

http://docs.aws.amazon.com/redshift/latest/dg/r_COPY.html