【问题标题】:Redshift. Convert comma delimited values into rows红移。将逗号分隔的值转换为行
【发布时间】:2014-09-26 13:19:52
【问题描述】:

我想知道如何将逗号分隔的值转换为 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      
....

【问题讨论】:

标签: amazon-web-services amazon-redshift


【解决方案1】:

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

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

具体来说,如果我们假设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) != '';

【讨论】:

  • 如果假设不成立,即user_action 等效项中的最大逗号数大于 等效cmd_logs 等效项中的行数,该怎么办?在我的情况下确实如此,我最终会失去价值——作为参考,我拥有的最大逗号数是 ~5,000
  • @daRknight 你总是可以从一个更大的表中找到SELECT,它会有你需要的行数——不管你使用什么表,不管怎样,它几乎都是一个虚拟表。如果您的 Redshift 具有generate_series() 功能(请参阅this question),您可以只使用SELECT generate_series AS n FROM generate_series(1, 10000)。或者,请参阅this answer
【解决方案2】:

另一个想法是首先将您的 CSV 字符串转换为 JSON,然后是 JSON 提取,如下所示:

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

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

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

【讨论】:

    【解决方案3】:

    您可以通过以下查询获得预期结果。我正在使用“UNION ALL”将列转换为行。

    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
    

    【讨论】:

    • 为什么会被否决?这是最干净的工作解决方案。然后你只需要摆脱空值(如果请求的位置没有值,它将返回一个空字符串)
    • 这仅指定使用 3 个逗号分隔的值。
    【解决方案4】:

    这是我同样糟糕的答案。

    我有一个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 并重新上传 that 作为表格以提供帮助。

    就像我说的:一个糟糕的、不好的解决方案。

    【讨论】:

      【解决方案5】:

      聚会迟到了,但我有一些工作(虽然很慢)

      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的启发

      【讨论】:

        【解决方案6】:

        上面的答案只是改进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 子句并查看结果时才出现

        【讨论】:

          【解决方案7】:

          创建一个将动态解析字符串并填充临时表的存储过程,从临时表中选择。

          这里是神奇的代码:-

            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
          
          

          【讨论】:

            【解决方案8】:

            如果您知道您的 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 name num_actions actions
            2 Gregory 3 find,diagnose,taunt
            3 Robot 2 kill,destroy
            1 Shone 3 start,stop,cancel

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

            子查询以展平动作

            这是取消嵌套的查询:

            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 Shone start
            1 Shone stop
            1 Shone cancel
            2 Gregory find
            2 Gregory diagnose
            2 Gregory taunt
            3 Robot kill
            3 Robot destroy

            【讨论】:

              【解决方案9】:

              您可以尝试使用 copy 命令将文件复制到 redshift 表中

              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 ','
              

              您可以使用 delimiter ',' 选项。

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

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

              【讨论】:

              猜你喜欢
              • 2017-12-13
              • 1970-01-01
              • 1970-01-01
              • 2012-12-02
              • 2021-09-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-06-18
              相关资源
              最近更新 更多