【问题标题】:Split comma separated values into target table with fixed number of columns将逗号分隔值拆分为具有固定列数的目标表
【发布时间】:2021-02-20 11:46:09
【问题描述】:

我在 Postgres 13.1 数据库中有一个包含单列的表。它由许多行以逗号分隔的值组成 - 最多大约 20 个元素。

我想将数据拆分为多列。但是我只有有限数量的列,例如单行中的 5 个和超过 5 个 CSV 值,因此必须将多余的值转移到新的/下一行)。如何做到这一点?

例子:

a1, b1, c1
a2, b2, c2, d2, e2, f2
a3, b3, c3, d3, e3, f3, g3, h3, i3, j3
a4
a5, b5, c5
'
'
'

列只有 5 列,所以输出如下:

c1 c2 c3 c4 c5
---------------
a1 b1 c1
a2 b2 c2 d2 e2 
f2
a3 b3 c3 d3 e3
f3 g3 h3 i3 j3
a4
a5 b5 c5
'
'
'

【问题讨论】:

  • 结果行的顺序重要吗?如果有,怎么做?
  • @ErwinBrandstetter 是的,它们的顺序。顺序中没有逻辑部分。谢谢
  • @ErwinBrandstetter 你好。是否有任何函数或查询将右侧的值放入列中?比如c5下的a1值,c4下的b1,c3下的c1?

标签: sql postgresql split


【解决方案1】:

将 CSV 值存储在单列中通常是一种糟糕的设计。如果可能,请改用数组或适当的规范化设计。

虽然坚持你目前的情况......

对于已知的小最大元素数

没有诡计或递归的简单解决方案就可以了:

SELECT id, 1 AS rnk
     , split_part(csv, ', ', 1) AS c1
     , split_part(csv, ', ', 2) AS c2
     , split_part(csv, ', ', 3) AS c3
     , split_part(csv, ', ', 4) AS c4
     , split_part(csv, ', ', 5) AS c5
FROM   tbl
WHERE  split_part(csv, ', ', 1) <> '' -- skip empty rows

UNION ALL
SELECT id, 2
     , split_part(csv, ', ', 6)
     , split_part(csv, ', ', 7)
     , split_part(csv, ', ', 8)
     , split_part(csv, ', ', 9)
     , split_part(csv, ', ', 10)
FROM   tbl
WHERE  split_part(csv, ', ', 6) <> '' -- skip empty rows

-- three more blocks to cover a maximum "around 20"

ORDER  BY id, rnk;

db小提琴here

id是原表的PK。
显然,这假定 ', ' 作为分隔符。
您可以轻松适应。

相关:

对于未知数量的元素

各种方式。一种方法是在取消嵌套之前使用regexp_replace() 替换每五个分隔符...

-- for any number of elements
SELECT t.id, c.rnk
     , split_part(c.csv5, ', ', 1) AS c1
     , split_part(c.csv5, ', ', 2) AS c2
     , split_part(c.csv5, ', ', 3) AS c3
     , split_part(c.csv5, ', ', 4) AS c4
     , split_part(c.csv5, ', ', 5) AS c5
FROM   tbl t
     , unnest(string_to_array(regexp_replace(csv, '((?:.*?,){4}.*?),', '\1;', 'g'), '; ')) WITH ORDINALITY c(csv5, rnk)
ORDER  BY t.id, c.rnk;

db小提琴here

这假定所选的分隔符 ; 从不 出现在您的字符串中。 (就像,永远不会出现。)

正则表达式模式是关键:'((?:.*?,){4}.*?),'

(?:) ... “non-capturing” set of parentheses
() ... “capturing” set of parentheses
*? ... non-greedy quantifier
{4}? ... 正好 4 个匹配的序列

替换 '\1;' 包含 back-reference \1

'g'作为第四个函数参数需要重复替换。

进一步阅读:

解决此问题的其他方法包括递归 CTE 或集合返回函数 ...

从右到左填充

(就像您在 How to put values starting from the right side into columns? 中添加的一样)
只需倒数如下数字:

SELECT t.id, c.rnk
     , split_part(c.csv5, ', ', 5) AS c1
     , split_part(c.csv5, ', ', 4) AS c2
     , split_part(c.csv5, ', ', 3) AS c3
     , split_part(c.csv5, ', ', 2) AS c4
     , split_part(c.csv5, ', ', 1) AS c5
FROM ...

db小提琴here

【讨论】:

  • 非常感谢您的帮助。问题 - '' 这在 WHERE 子句中是什么意思?虽然我对此很陌生。有没有什么办法可以用硬编码来做,比如如果将来有超过 20 个元素的行怎么办?请帮忙。
  • @Martin2000:&lt;&gt; is the "not equal" operator。如果第一列不等于空字符串 (''),则该行不是噪声。见:stackoverflow.com/a/23767625/939860
  • @Martin2000:我为任意数量的元素添加了解决方案,并从右到左填充。
【解决方案2】:
CREATE UNLOGGED TABLE foo( x TEXT );
\copy foo FROM stdin
a1, b1, c1
a2, b2, c2, d2, e2, f2
a3, b3, c3, d3, e3, f3, g3, h3, i3, j3
a4
a5, b5, c5
\.

从行到单列...

SELECT (ROW_NUMBER() OVER () - 1)/5 AS r, u FROM (SELECT unnest(string_to_array(x,', ')) u from foo) y;
 r | u
---+----
 0 | a1
 0 | b1
 0 | c1
 0 | a2
 0 | b2
 1 | c2
 1 | d2
...etc

...然后回到已知长度的行。

SELECT r,array_agg(u) a FROM (
 SELECT (ROW_NUMBER() OVER () - 1)/5 AS r, u FROM (
  SELECT unnest(string_to_array(x,', ')) u from foo) y) y1 
GROUP BY r ORDER BY r;
 r |    a
---+------------------
 0 | {a1,b1,c1,a2,b2}
 1 | {c2,d2,e2,f2,a3}
 2 | {b3,c3,d3,e3,f3}
 3 | {g3,h3,i3,j3,a4}
 4 | {a5,b5,c5}

在此之后,您可以使用 a[] 将其插入到表格中的每一列。最后一行如何处理留给读者作为练习......

【讨论】:

    【解决方案3】:

    相关问题解答:How to put values starting from the right side into columns?

    接受的great answer from @ErwinBrandstetter 可以轻松适应所需的从右到左输出。

    您只需要更改拆分部分的顺序。因此,您不会返回拆分部分 1-5 和 6-10,而是返回 5-1 和 10-6:

    demo:db<>fiddle

    SELECT id, 1 AS rnk
         , split_part(csv, ', ', 5) AS c1
         , split_part(csv, ', ', 4) AS c2
         , split_part(csv, ', ', 3) AS c3
         , split_part(csv, ', ', 2) AS c4
         , split_part(csv, ', ', 1) AS c5
    FROM   tbl
    WHERE  split_part(csv, ', ', 1) <> '' -- skip empty rows
    
    UNION ALL
    SELECT id, 2
         , split_part(csv, ', ', 10)
         , split_part(csv, ', ', 9)
         , split_part(csv, ', ', 8)
         , split_part(csv, ', ', 7)
         , split_part(csv, ', ', 6)
    FROM   tbl
    WHERE  split_part(csv, ', ', 6) <> '' -- skip empty rows
    
    -- more?
    
    ORDER  BY id, rnk;
    

    【讨论】:

    • “强逻辑”是什么意思?抱歉,这个问题我不太清楚。
    【解决方案4】:

    您需要在您使用的任何后端层中执行此操作。

    首先,将 CSV 行转换为字符串数组

    然后,使用类似这样的逻辑将值添加到数据库中

    int row = 0; // database row index - can be used to just have a count
    
    final int MAX_COLUMNS = 5;
    
    for(int i = 0; i<rows.length; i++) {
        // Convert csv row string to array of each value.
        String [] values = rows[i].split(",");
    
        // Dividing whole row into chunks of size of number of columns
    
        for(int j = 0; j < (values.length/(MAX_COLUMNS)) + 1; j++) {
           Add Values [MAX_COLUMNS*j,MAX_COLUMNS*j+(MAX_COLUMNS - 1)] to the row [row + j]
          row++;
        }
    
    }
    

    【讨论】:

    • 您能在 Postgres 中告诉我吗?我是新手,不知道您使用的语法或如何将您的语法转换为 Postgres。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    • 2018-09-25
    • 2012-05-21
    • 1970-01-01
    相关资源
    最近更新 更多