【问题标题】:split comma separated values into columns dynamically将逗号分隔的值动态拆分为列
【发布时间】:2018-09-25 20:07:08
【问题描述】:

我正在尝试找到一个 sql 来将我在一列中的逗号分隔值拆分为单独的列。我发现了几个类似的问题,但没有一个答案能够处理未来行中分离值增加的情况。是不是不能在 SQL 中这样做,而 PL/SQL 是唯一的解决方案?

Example  Data
col1
val1,val2,val3,val4... 
valA,valB,valC

Expected output
col1 col2 col3 col4 .....
val1 val2 val3 val4 .....
valA valB valC null .....

注意:因此,如果当前一行中逗号分隔值的最大值为 200,那么我可以在 select 子句中对 200 个 regexp_substr() 函数进行硬编码,但是如果将来在新行中添加 205 个逗号分隔值怎么办?目前如何在sql中处理这种未来可能的情况。

【问题讨论】:

  • 如果你知道你的数据总是用逗号分隔,那么你可以数一下逗号,它会告诉你需要多少列。动态计算的列数 = 逗号计数/2。只是一个想法。你可以围绕这个构建你的逻辑。

标签: sql oracle plsql split


【解决方案1】:

不要使用列 - 如果您需要将其转换为列,则在您用于从数据库读取的任何客户端上动态执行此操作,并将结果作为查询中的行返回,并带有关联的索引以指示哪一列它应该在里面。

Oracle 中有many, many ways 用于分割分隔字符串。

不使用正则表达式的是:

SQL Fiddle

Oracle 11g R2 架构设置

CREATE TABLE data ( cols ) AS
  SELECT 'col1' FROM DUAL UNION ALL
  SELECT 'val1,val2,val3,val4' FROM DUAL UNION ALL
  SELECT 'valA,valB,valC' FROM DUAL;

查询 1

WITH bounds ( id, list, start_pos, end_pos, lvl ) AS (
  SELECT ROWNUM,
         cols,
         1,
         INSTR( cols, ',' ),
         1
  FROM   data
UNION ALL
  SELECT id,
         list,
         end_pos + 1,
         INSTR( list, ',', end_pos + 1 ),
         lvl + 1
  FROM   bounds
  WHERE  end_pos > 0
)
SELECT id,
       SUBSTR(
         list,
         start_pos,
         DECODE( end_pos, 0, LENGTH( list ) + 1, end_pos ) - start_pos
       ) AS item,
       lvl,
       MAX( lvl ) OVER () AS num_columns
FROM   bounds
ORDER BY id, lvl

Results

| ID | ITEM | LVL | NUM_COLUMNS |
|----|------|-----|-------------|
|  1 | col1 |   1 |           4 |
|  2 | val1 |   1 |           4 |
|  2 | val2 |   2 |           4 |
|  2 | val3 |   3 |           4 |
|  2 | val4 |   4 |           4 |
|  3 | valA |   1 |           4 |
|  3 | valB |   2 |           4 |
|  3 | valC |   3 |           4 |

查询 2

如果您想在纯 SQL 中将输出转换为行,那么您需要知道最大列数,如果您这样做,那么您可以使用 PIVOT(这就是为什么,因为您不知道似乎有一个固定的最大值,我说是按行输出并在客户端进行转换):

WITH bounds ( id, list, start_pos, end_pos, lvl ) AS (
  SELECT ROWNUM,
         cols,
         1,
         INSTR( cols, ',' ),
         1
  FROM   data
UNION ALL
  SELECT id,
         list,
         end_pos + 1,
         INSTR( list, ',', end_pos + 1 ),
         lvl + 1
  FROM   bounds
  WHERE  end_pos > 0
),
items ( id, item, col ) AS (
  SELECT id,
         SUBSTR(
           list,
           start_pos,
           DECODE( end_pos, 0, LENGTH( list ) + 1, end_pos ) - start_pos
         ),
         lvl
  FROM   bounds
)
SELECT *
FROM   items
PIVOT  (
  MAX( item ) FOR col IN (
    1 AS col1,
    2 AS col2,
    3 AS col3,
    4 AS col4
  )
)
ORDER BY id

Results

| ID | COL1 |   COL2 |   COL3 |   COL4 |
|----|------|--------|--------|--------|
|  1 | col1 | (null) | (null) | (null) |
|  2 | val1 |   val2 |   val3 |   val4 |
|  3 | valA |   valB |   valC | (null) |

如果最大列数发生变化,您可以通过更改末尾的 PIVOT 部分并硬编码新的最大列数来轻松更新查询。

如果您不知道最大值并且必须在数据库中进行,那么您将需要使用 PL/SQL 来生成 dynamic query,但这不太可能是高效的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-06
    • 2012-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多