Skip to content
Advertisement

Generate rows from JSON (SDO_GEOMETRY)

I have a multi-part SDO_GEOMETRY that I can represent as JSON (Oracle 21c):

select
    json_object(* PRETTY) as  pretty_json
from
   (
    select
        '001' as line_id,
        sdo_geometry('MULTILINESTRING ((0.0 5.0, 10.0 10.0, 30.0 0.0), (50.0 10.0, 60.0 10.0))') as sdo_geom
    from
        dual
    )

PRETTY_JSON
--------------------------
{
  "LINE_ID" : "001",
  "SDO_GEOM" :
  {
    "SDO_GTYPE" : 2006,
    "SDO_SRID" : null,
    "SDO_POINT" :
    {
    },
    "SDO_ELEM_INFO" :
    [
      1,
      2,
      1,
      7,
      2,
      1
    ],
    "SDO_ORDINATES" :
    [
      0,
      5,
      10,
      10,
      30,
      0,
      50,
      10,
      60,
      10
    ]
  }
}

As an experiment, I want to find a way to generate rows from the JSON:

   LINE_ID   PART_NUM VERTEX_NUM          X          Y 
---------- ---------- ---------- ---------- ---------- 
       001          1          1          0          5 
       001          1          2         10         10 
       001          1          3         30          0 
       001          2          1         50         10 
       001          2          2         60         10 

Each row is a vertex of the geometry.

Question:

In an SQL query, is there a way to generate rows for each of the JSON vertices?


I’m aware that SDO_GEOMETRY has a GetVertices() function that can be used for this — it avoids the JSON step. That works, but in my case, I want to experiment with parsing vertices out of JSON text using SQL. If I find something that works, I want to use that same JSON extraction technique on a user-defined datatype that doesn’t have a GetVertices function.

Advertisement

Answer

The simplest method (adapted from my answer to your previous question for 3D lines) is to use SDO_GEOMETRY and not to use JSON functions:

SELECT t.line_id,
       e.elem_no,
       v.id AS coord_id,
       x,
       y
FROM   ( SELECT '001' AS line_id,
                SDO_GEOMETRY(
                  'MULTILINESTRING ((0.0 5.0, 10.0 10.0, 30.0 0.0), (50.0 10.0, 60.0 10.0))'
                ) AS shape
         FROM   DUAL
       ) 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

But, if you assume that you are only working with 2D multi-lines (GTYPE = 2006) then you can get the output with JSON but its not simple:

WITH elements (line_id, elem_no, starting_offset, next_offset, ordinates) AS (
  SELECT '001' as line_id,
         elem_no,
         starting_offset,
         next_offset,
         ordinates
  FROM   JSON_TABLE(
           JSON_OBJECT(
             SDO_GEOMETRY(
               'MULTILINESTRING ((0.0 5.0, 10.0 10.0, 30.0 0.0), (50.0 10.0, 60.0 10.0))'
             )
           ),
           '$?(@.SDO_GTYPE==2006)'
           COLUMNS (
             ordinates       VARCHAR2(4000) FORMAT JSON PATH '$.SDO_ORDINATES',
             NESTED PATH '$.SDO_ELEM_INFO[*]' COLUMNS (
               item_no FOR ORDINALITY,
               item    NUMBER PATH '$'
             )
           )
         )
  MATCH_RECOGNIZE(
    ORDER BY item_no
    MEASURES
      MATCH_NUMBER() AS elem_no,
      FIRST(starting_offset.item) AS starting_offset,
      FIRST(etype.item)           AS etype,
      FIRST(interpretation.item)  AS interpretation,
      NEXT(item)                  AS next_offset,
      FIRST(ordinates)            AS ordinates
    PATTERN (starting_offset etype interpretation)
    DEFINE starting_offset AS 1 = 1
  ) m
  WHERE (etype, interpretation) IN ((2, 1))
)
SELECT m.*
FROM   elements e
       CROSS JOIN LATERAL(
         SELECT *
         FROM   JSON_TABLE(
                  e.ordinates,
                  '$[*]'
                  COLUMNS (
                    coord_no FOR ORDINALITY,
                    coord    NUMBER PATH '$'
                  )
                )
         WHERE  coord_no >= e.starting_offset
         AND    (e.next_offset IS NULL OR coord_no < e.next_offset)
       )
MATCH_RECOGNIZE (
  PARTITION BY line_id, elem_no
  ORDER BY coord_no
  MEASURES
    MATCH_NUMBER() AS coord_id,
    FIRST(x.coord) AS X,
    FIRST(y.coord) AS Y
  PATTERN (X Y)
  DEFINE X AS 1 = 1
) m

Which both output:

LINE_ID ELEM_NO COORD_ID X Y
001 1 1 0 5
001 1 2 10 10
001 1 3 30 0
001 2 1 50 10
001 2 2 60 10

db<>fiddle here

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement