从数字字符串生成行

Generate rows from string of numbers

我有一个 Oracle 18c table,它有这样的字符串:

select
    '((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))' as multipart_lines
    --There are more rows in the actual table.
from
    dual

    MULTIPART_LINES                                              
    -------------------------------------------------------------
    ((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))
--      v1      v2           v3           v4           v5
--  |            part 1             | |          part 2         | 

在查询中,我想为每个顶点生成行:

PART_NUM   VERTEX_NUM          X          Y          Z
---------- ---------- ---------- ---------- ----------
         1          1          0          5          0
         1          2         10         10      11.18
         1          3         30          0      33.54
         2          1         50         10      33.54
         2          2         60         10      43.54

如何从字符串中的数字(顶点)生成行?

相关:Oracle Ideas - generate_series() function

如果输入是某种标准格式 - 例如 JSON,那就简单多了。那么这项任务将是微不足道的。你有什么权力吗?

如果不是,您可以将输入转换为正确的 JSON(或类似的),或者您可以直接解决问题。我在下面说明后者,假设 Oracle 12.1 或更高版本。

with
  inputs (id, multipart_lines) as (
    select 2810,
      '((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))'
                                  from dual union all
    select 7284, '((-2.3 0.2 3))' from dual
  )
select id, part_num, vertex_num, x, y, z
from   inputs 
       cross join lateral
       ( select  level as part_num,
                 regexp_substr(multipart_lines,
                               '\(([^()]+)\)', 1, level, null, 1) as part
         from    dual
         connect by level <= regexp_count(multipart_lines, '\(') - 1
       )
       cross join lateral
       (
         select  level as vertex_num,
                 regexp_substr(part, '[^,]+', 1, level) as vertex
         from    dual
         connect by level <= regexp_count(part, ',') + 1
       )
       cross join lateral
       (
         select   to_number(regexp_substr(vertex, '[^ ]+', 1, 1)) as x,
                  to_number(regexp_substr(vertex, '[^ ]+', 1, 2)) as y,
                  to_number(regexp_substr(vertex, '[^ ]+', 1, 3)) as z
         from     dual
       )
order by id, part_num, vertex_num   --  if needed
;

输出(来自我在查询中包含的示例输入):

        ID   PART_NUM VERTEX_NUM          X          Y          Z
---------- ---------- ---------- ---------- ---------- ----------
      2810          1          1          0          5          0
      2810          1          2         10         10      11.18
      2810          1          3         30          0      33.54
      2810          2          1         50         10      33.54
      2810          2          2         60         10      43.54
      7284          1          1       -2.3         .2          3

我看到那个数学家到了那里。我正在使用 from json_table 但我不能一次取消嵌套 2 行,否则我基本上会在那里第二次使用 row_number() 超过(按 Paru_num 分区)。

create table sample(value varchar(100));
insert into sample values 
('((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))')

1 行受影响

with a as (
select '["'|| regexp_replace(value,'\(|\)','') ||'"]' a from sample
),
b as (
select regexp_replace(a,', ?','","') b from a
),
c as (
SELECT value c
FROM json_table( (select b from b) , '$[*]'
                COLUMNS (value PATH '$')
               )
),
d as (
SELECT
  c d,
  instr(c,' ') s1,
  instr(c,' ',instr(c,' ')+1) s2
from c)
select 
 substr(d,0,s1) x,
 substr(d,s1+1,s2-s1) y,
 substr(d,s2+1) z
from d
X   | Y   | Z    
:-- | :-- | :----
0   | 5   | 0    
10  | 10  | 11.18
30  | 0   | 33.54
50  | 10  | 33.54
60  | 10  | 43.54
with a as (
select '["'|| regexp_replace(value,'\(+|\)+','"') ||'"]' a from sample
),
b as(
select replace(a,'""','"')b from a
),
c as (
SELECT 
  row_number() over (order by 'zero') pn,
  value c
FROM json_table( (select b from b) , '$[*]'
                COLUMNS (value PATH '$')
)    ),
d as (
select '["'|| pn ||' '|| regexp_replace(c,', ?','","'||pn||' ')||'"]' d from c
)
select * from d
| D                                          |
| :----------------------------------------- |
| ["1 0 5 0","1 10 10 11.18","1 30 0 33.54"] |
| ["2 50 10 33.54","2 60 10 43.54"]          |
with a as (
select '["'|| regexp_replace(value,'\(+|\)+','"') ||'"]' a from sample
),
b as(
select replace(a,'""','"')b from a
),
c as (
SELECT 
  row_number() over (order by 'zero') pn,
  value c
FROM json_table( (select b from b) , '$[*]'
                COLUMNS (value PATH '$')
)    ),
d as (
select '["'|| pn ||' '|| regexp_replace(c,', ?','","'||pn||' ')||'"]' d from c
),
e as (
SELECT 
  row_number() over (order by 'zero') pn,
  value c
FROM json_table( (select d from d) , '$[*]'
                COLUMNS (value PATH '$')
)    )
select * from e
ORA-01427: single-row subquery returns more than one row

db<>fiddle here

作为替代方案 - 以下是您可以如何处理输入字符串以将它们转换为正确的 JSON 字符串;那么任务就变得微不足道了。首先单独显示 JSON-ization,因为它确实是该解决方案中有意义的部分;然后在显示查询和结果后,我将通过添加 JSON 操作来完成解决方案。

with
  inputs (id, multipart_lines) as (
    select 2810,
      '((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))'
                                  from dual union all
    select 7284, '((-2.3 0.2 3))' from dual
  )
, j (id, ml) as (
    select id,
           regexp_replace(
             regexp_replace(
               regexp_replace(
                 regexp_replace(
                   regexp_replace(multipart_lines
                   , '\(\s*\(\s*', '[[[')
                 , '\s*\)\s*\)', ']]]')
               , '\s*\)\s*,\s*\(\s*', '],[')
             , '\s*,\s*', '],[')
           , '\s+', ',')
    from   inputs
  )
select * from j;



   ID ML                                                                  
----- --------------------------------------------------------------------
 2810 [[[0,5,0],[10,10,11.18],[30,0,33.54]],[[50,10,33.54],[60,10,43.54]]]
 7284 [[[-2.3,0.2,3]]] 

您的输入应该看起来像我的子查询 j 中列 ml 中的字符串 - 然后您可以像这样处理它们:

with
  inputs (id, multipart_lines) as (
            ........
  )
, j (id, ml) as (
            ........
  )
select id, part_num, vertex_num, x, y, z
from   j,
       json_table(ml, '$[*]'
                  columns (
                    part_num for ordinality,
                    nested path '$[*]'
                    columns (
                      vertex_num for ordinality,
                      x number path '$[0]',
                      y number path '$[1]',
                      z number path '$[2]'
                    )
                  )
       )
order by id, part_num, vertex_num   --  if needed
;

输出与我的其他答案相同。

@SolomonYakobson 在 Oracle Community post.

中提供了这个答案
with sample as (
                select '((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))' as multipart_lines
                        --There are more rows in the actual table.
                  from  dual
               )
select  part_num,
        vertex_num,
        to_number(regexp_substr(vertex,'[^ ]+')) x,
        to_number(regexp_substr(vertex,'[^ ]+',1,2)) y,
        to_number(regexp_substr(vertex,'[^ ]+',1,3)) z
  from  sample,
        lateral(
                select  level part_num,
                        regexp_substr(multipart_lines,'\(([^()]+)',1,level,null,1) part
                  from  dual
                  connect by level < regexp_count(multipart_lines,'\(')
               ),
        lateral(
                select  level vertex_num,
                        regexp_substr(part,'[^,]+',1,level) vertex
                  from  dual
                  connect by level <= regexp_count(part,',') + 1
               )
/

  PART_NUM VERTEX_NUM          X          Y          Z
---------- ---------- ---------- ---------- ----------
         1          1          0          5          0
         1          2         10         10      11.18
         1          3         30          0      33.54
         2          1         50         10      33.54
         2          2         60         10      43.54

以及 Oracle 19c 解决方案:

create or replace
  function split_multipart_line(
                                p_line varchar2
                               )
    return varchar2
    sql_macro
    is
    begin
        return q'[
select  part_num,
        vertex_num,
        to_number(regexp_substr(vertex,'[^ ]+')) x,
        to_number(regexp_substr(vertex,'[^ ]+',1,2)) y,
        to_number(regexp_substr(vertex,'[^ ]+',1,3)) z
  from  dual,
        lateral(
                select  level part_num,
                        regexp_substr(p_line,'\(([^()]+)',1,level,null,1) part
                  from  dual
                  connect by level < regexp_count(p_line,'\(')
               ),
        lateral(
                select  level vertex_num,
                        regexp_substr(part,'[^,]+',1,level) vertex
                  from  dual
                  connect by level <= regexp_count(part,',') + 1
               )]';
end;
/

Function created.

SQL> with sample as (
  2                  select '((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))' multipart_lines from dual union all
  3                  select '((1 2 3, 4 5 6, 7 8 9, 10 11 12),(22 33 44, 55 66 77))' multipart_lines from dual
  4                 )
  5  select  l.*
  6    from  sample,
  7          lateral(
  8                  select  *
  9                    from  split_multipart_line(multipart_lines)
 10                 ) l
 11  /


  PART_NUM VERTEX_NUM          X          Y          Z
---------- ---------- ---------- ---------- ----------
         1          1          0          5          0
         1          2         10         10      11.18
         1          3         30          0      33.54
         2          1         50         10      33.54
         2          2         60         10      43.54
         1          1          1          2          3
         1          2          4          5          6
         1          3          7          8          9
         1          4         10         11         12
         2          1         22         33         44
         2          2         55         66         77


11 rows selected.

SQL>

你可以只用简单的字符串函数(比正则表达式快得多)和递归 sub-queries:

WITH line_bounds (id, multipart_lines, line_no, spos, epos) AS (
    SELECT id,
           multipart_lines,
           1,
           2,
           INSTR(multipart_lines, ')', 2)
    FROM   table_name
UNION ALL
    SELECT id,
           multipart_lines,
           line_no + 1,
           epos + 2,
           INSTR(multipart_lines, ')', epos + 2)
    FROM   line_bounds
    WHERE  epos > 0
)
SEARCH DEPTH FIRST BY id SET line_order,
row_bounds (id, line, line_no, row_no, spos, epos) AS (
  SELECT id,
         SUBSTR(multipart_lines, spos + 1, epos - spos - 1),
         line_no,
         1,
         1,
         INSTR(
           SUBSTR(multipart_lines, spos + 1, epos - spos - 1),
           ',',
           1
         )
  FROM   line_bounds
  WHERE  epos > 0
UNION ALL
  SELECT id,
         line,
         line_no,
         row_no + 1,
         epos + 2,
         INSTR(line, ',', epos + 2)
  FROM   row_bounds
  WHERE  epos > 0
)
SEARCH DEPTH FIRST BY id, line_no SET row_order,
column_bounds (id, line, line_no, row_no, xpos, ypos, zpos, epos) AS (
  SELECT id,
         line,
         line_no,
         row_no,
         spos,
         INSTR(line, ' ', spos, 1),
         INSTR(line, ' ', spos, 2),
         epos
  FROM   row_bounds
)
SELECT id,
       line_no,
       row_no,
       SUBSTR(line, xpos + 0, ypos - xpos) AS x,
       SUBSTR(line, ypos + 1, zpos - ypos - 1) AS y,
       CASE epos
       WHEN 0
       THEN SUBSTR(line, zpos + 1)
       ELSE SUBSTR(line, zpos + 1, epos - zpos - 1)
       END AS z
FROM   column_bounds

其中,对于示例数据:

CREATE TABLE table_name (id, multipart_lines) AS
  SELECT 1, '((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))' FROM DUAL UNION ALL
  SELECT 2, '((0 1 0, 0 2 0, 0 3 0),(0 4 0, 0 5 0))' FROM DUAL;

输出:

ID LINE_NO ROW_NO X Y Z
1 1 1 0 5 0
1 1 2 10 10 11.18
1 1 3 30 0 33.54
1 2 1 50 10 33.54
1 2 2 60 10 43.54
2 1 1 0 1 0
2 1 2 0 2 0
2 1 3 0 3 0
2 2 1 0 4 0
2 2 2 0 5 0

db<>fiddle here

使用SDO_GEOMETRY解析字符串然后使用SDO_UTIL.EXTRACT获取每个元素然后SDO_UTIL.GETVERTICES获取顶点:

SELECT t.id,
       e.elem_no,
       v.id AS coord_id,
       x,
       y,
       z
FROM   ( SELECT id,
                SDO_GEOMETRY('MULTILINESTRING '||multipart_lines) AS shape
         FROM   table_name
       ) t
       CROSS JOIN LATERAL (
         SELECT LEVEL AS elem_no,
                SDO_UTIL.EXTRACT(t.shape, LEVEL) AS elem
         FROM   DUAL
         CONNECT BY LEVEL <= SDO_UTIL.GETNUMELEM(t.shape)
       ) e
       CROSS APPLY TABLE(SDO_UTIL.GETVERTICES(e.elem)) v

其中,对于示例数据:

CREATE TABLE table_name (id, multipart_lines) AS
  SELECT 1, '((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))' FROM DUAL UNION ALL
  SELECT 2, '((0 1 0, 0 2 0, 0 3 0),(0 4 0, 0 5 0))' FROM DUAL;

输出:

ID ELEM_NO COORD_ID X Y Z
1 1 1 0 5 0
1 1 2 10 10 11.18
1 1 3 30 0 33.54
1 2 1 50 10 33.54
1 2 2 60 10 43.54
2 1 1 0 1 0
2 1 2 0 2 0
2 1 3 0 3 0
2 2 1 0 4 0
2 2 2 0 5 0

db<>fiddle here