# Filtering Rings (Oracle Spatial)

Note: This article was edited on 1st March 2012 to cope with situation where all inner rings are filtered away by the change to using SDO_AGGR_SET_UNION which is not subject to the same licensing as SDO_AGGR_UNION. The changes are reflected only in the Filter_Rings PL/SQL function at the end of this article. In addition, a pure PL/SQL version of the Filter_Rings function is presented that is SDO_DIFFERENCE free as well.

I recently wrote an article Filtering Rings in Polygon ( PostGIS ).

How can we do this for Oracle?

If you recall I have written an article on How to extract elements from the result of an sdo_intersection of two polygons.

Well we can combine these two generate a solution to the filtering problem.

First, note how we can construct an Oracle SDO_GEOMETRY from a WKT input:

``` SELECT mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5  7,7 7,7 5,5 5))',NULL) AS geom
FROM DUAL;
--
GEOM
------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1,11,2003,1,21,2003,1),MDSYS.SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0,10,10,10,11,11,11,11,10,10,10,5,5,5,7,7,7,7,5,5,5))
--
1 ROWS selected
```

Now there are a few approaches we could take all on the basis of the SDO_UTIL.EXTRACT function and the GetNumRings function I wrote for the article Elem_Info_Array Processing: An alternative to SDO_UTIL.GetNumRings and querying SDO_ELEM_INFO itself

1. Generate_Series

Firstly, in the article generate_series: an Oracle implementation in light of SQL Design Patterns in which I showed a number of methods for generating an integer series of numbers. We can use this function with the GetNumRings and Extract functions as follows.

``` SELECT GetNumRings(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL)) AS numRings
FROM DUAL;
--
NUMRINGS
--------
3
--
1 rows selected
SELECT b.column_value AS ring_no,
Sdo_Util.EXTRACT(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL),1,b.column_value) AS ring
FROM TABLE(geom.generate_series(1,
sdo_util.GetNumRings(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL)),
1)) b;
--
RING_NO RING
------- ------------------------------------------------------------------------------------------------------------------------------
1       MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0))
2       MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(10,10,11,10,11,11,10,11,10,10))
3       MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(5,5,7,5,7,7,5,7,5,5))
--
3 ROWS selected
```

To filter the rings and rebuild we can do the following:

``` SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)).Get_WKT() AS filtered_geom
FROM (SELECT b.column_value AS ring_no,
Sdo_Util.EXTRACT(mdsys.sdo_geometry('POLYGON((10 10,10 20,20 20,20 10,10 10),(0 0,0 1,1 1,1 0,0 0),(5 5,5 7,7 7,7 5,5 5))',NULL),1,b.column_value) AS ring
FROM TABLE(geom.generate_series(
1,
GetNumRings(mdsys.sdo_geometry('POLYGON((10 10,10 20,20 20,20 10,10 10),(0 0,0 1,1 1,1 0,0 0),(5 5,5 7,7 7,7 5,5 5))',NULL)),
1)) b
) c
WHERE c.ring_no = 1
OR ( c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2);
--
FILTERED_GEOM
-------------------------------------------------------------
POLYGON ((20.0 20.0, 0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0))
--
1 ROWS selected
```

While we end up with a POLYGON you will note that the inner ring has been dissolved into the polygon. This is because we used the SDO_AGGR_UNION aggregate operator. There is no other useful aggregate operator that we can use that will maintain any inner rings. So, we will have to resort to “uglier” SQL to achieve our aim.

In the Oracle “toolkit” there is the SDO_DIFFERENCE geoprocessing operator. This will do what we want but it is not an aggregate operator. The operator acts on two single SDO_GEOMETRY objects so will will have to “serve up” to this operator the outer ring separately from the inner rings (which can be aggregated).

``` SELECT SDO_GEOM.SDO_DIFFERENCE(a.outer_ring,d.inner_rings,0.05).Get_WKT() AS filtered_geom
FROM (SELECT sdo_util.EXTRACT(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL),1,1) AS outer_ring
3           FROM DUAL) a,
4        (SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)) AS inner_rings
5           FROM (SELECT b.column_value as ring_no,
6                        Sdo_Util.EXTRACT(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL),1,b.column_value) AS ring
7                   FROM TABLE(geom.generate_series(
8                           1,
9                           GetNumRings(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL)),
10                           1)) b
11                ) c
12          WHERE c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2
13*       ) d;
--
FILTERED_GEOM
-----------------------------------------------------------------------------------------------------------
POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0, 5.0 7.0))
--
1 ROWS selected
```

Which is exactly what we want.

Put in a function….

2. Use of Geom.ExtractElementsPiped

But what if you don’t want to use generate_series? I have encapsulated element extraction into the the ExplodeGeometry or ExtractElementsPiped functions in my GEOM package. We can use these to do the function extraction as follows:

``` SELECT b.geometry
FROM TABLE(geom.ExtractElementsPiped(mdsys.sdo_geometry('POLYGON((10 10,10 20,20 20,20 10,10 10),(0 0,0 1,1 1,1 0,0 0),(5 5,5 7,7 7,7 5,5 5))',NULL),0) ) b;
--
GEOMETRY
------------------------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0))
MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(10,10,11,10,11,11,10,11,10,10))
MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(5,5,7,5,7,7,5,7,5,5))
--
3 ROWS selected
```

But, as we learned above, we cannot just filter these polygons and reconstruct the original polygon with SDO_AGGR_UNION. The final solution for this approach based on the last SQL of section 1 above would be:

``` SELECT SDO_GEOM.SDO_DIFFERENCE(a.outer_ring,d.inner_rings,0.05).Get_WKT() AS filtered_geom
FROM (SELECT sdo_util.EXTRACT(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL),1,1) AS outer_ring
FROM DUAL) a,
(SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)) AS inner_rings
FROM (SELECT rownum AS ring_no,
b.geometry AS ring
FROM TABLE(geom.ExtractElementsPiped(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL),0)) b
) c
WHERE c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2
) d;
--
FILTERED_GEOM
-----------------------------------------------------------------------------------------------------------
POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0, 5.0 7.0))
--
1 ROWS selected
```

3. Use of Hierarchical Query

Generate_Series provides for a range of integer number generation and is, in many ways, overkill for what we are doing here. We can use a simple hierarchical query to generate our ring numbers for SDO_UTIL.EXTRACT. However, Generate_Series (or ExtractElementsPiped etc) is needed if you are running on 9i as the following hierarchical query doesn’t work in 9i.

The final SQL if you use a hierarchical query is as follows:

``` SELECT SDO_GEOM.SDO_DIFFERENCE(a.outer_ring,d.inner_rings,0.05).Get_WKT() AS filtered_geom
FROM (SELECT sdo_util.Extract(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL),1,1) AS outer_ring
FROM DUAL) a,
(SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)) AS inner_rings
FROM (SELECT level AS ring_no,
Sdo_Util.EXTRACT(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL),1,LEVEL) AS ring
FROM DUAL
CONNECT BY LEVEL <= GetNumRings(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL) )
) c
WHERE c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2
) d;
--
FILTERED_GEOM
-----------------------------------------------------------------------------------------------------------
POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0, 5.0 7.0))
--
1 ROWS selected
```

4. Encapsulate in a Function

Regardless as to the approach taken to extract the elements, it will be cleaner to encapsulate our algorithm inside a function as follows.

NOTE: This function requires my GEOM package to be able to compile and run.

``` /** ----------------------------------------------------------------------------------------
* @function  : Filter_Rings
* @precis     : Function that allows a user to remove inner rings from a polygon/multipolygon
* based         on an area value.
* @version    : 1.0
* @usage      : FUNCTION Filter_Rings(p_geometry   in mdsys.sdo_geometry,
*                                     p_tolerance in number,
*                                     p_area      in number,
*                                     p_ring      in number := 0)
*                 RETURN mdsys.sdo_geometry DETERMINISTIC;
*               eg select Filter_Rings(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',null),
*                                      10,
*                                      0.05)
*                    from dual;
* @param      : p_geometry  : mdsys.sdo_geometry : Original polygon/multipolygon
* @param      : p_area      : number : Area in square srid units below which an inner ring is removed.
* @param      : p_tolerance : number : Standard Oracle diminfo tolerance.
* @Param      : p_ring      : number : The number of the internal ring to be removed
* @return    : mdsys.sdo_geometry : input geometry with any qualifying inner rings removed
* @history    : Simon Greener - December 2008 - Original coding.
**/
FUNCTION Filter_Rings(p_geometry IN mdsys.sdo_geometry,
p_tolerance IN NUMBER,
p_area      IN NUMBER,
p_ring      IN NUMBER := 0)
RETURN MDSYS.SDO_GEOMETRY
IS
v_geom mdsys.sdo_geometry;
v_ok   NUMBER;
BEGIN
If ( p_geometry IS NULL
or
Mod(p_geometry.sdo_gtype,10) NOT IN (3,7) ) THEN
raise_application_error(-20001,'p_geometry is null or is not a polygon',TRUE);
End If;
Begin
-- Can only use this if Enterprise Edition and SDO
SELECT 1
INTO v_Ok
FROM v\$version
WHERE banner LIKE '%Enterprise Edition%';
SELECT 1
INTO v_Ok
FROM dba_registry
WHERE comp_id = 'SDO';
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'Not licensed for use of sdo_difference/sdo_area',TRUE);
RETURN p_geometry;
END;
SELECT CASE WHEN e.inner_rings IS NULL THEN a.outer_rings ELSE mdsys.SDO_GEOM.SDO_DIFFERENCE(a.outer_rings,e.inner_rings,p_tolerance) END AS filtered_geom
INTO v_geom
FROM (SELECT 1 AS id, SDO_AGGR_SET_UNION(CAST(COLLECT(MDSYS.SDO_UTIL.Extract(p_geometry,level,1)) AS SDO_Geometry_Array),p_tolerance) AS outer_rings
FROM DUAL
CONNECT BY level <= &&defaultSchema..GEOM.getnumouterrings(p_geometry)
) a
LEFT OUTER JOIN
(SELECT 1 as id, SDO_AGGR_SET_UNION(d.geom_array, p_tolerance) AS inner_rings
FROM (SELECT CAST(COLLECT(c.ring) AS SDO_Geometry_Array) AS geom_array, count(*) AS geomCount, p_tolerance
FROM (SELECT s.geom_no,
level AS ring_no,
MDSYS.SDO_UTIL.EXTRACT(s.geom,s.geom_no,LEVEL) AS ring
FROM (SELECT r.geom_no, r.geom
FROM (SELECT level AS geom_no,
MDSYS.SDO_UTIL.EXTRACT(p_geometry,level) AS geom
FROM DUAL
CONNECT BY LEVEL <= &&defaultSchema..GEOM.getNumOuterRings(p_geometry)
) r
WHERE &&defaultSchema..GEOM.GetNumInnerRings(r.geom) > 0
) s
CONNECT BY level <= &&defaultSchema..GEOM.GetNumRings(s.geom)
) c
WHERE c.ring_no > 1
AND MDSYS.SDO_GEOM.SDO_AREA(c.ring,p_tolerance) > p_area
AND c.ring_no <> p_ring
) d
WHERE d.geomcount > 0
) e
ON (e.id = a.id);
RETURN v_geom;
END Filter_Rings;
```

I have not extended this function to allow it to handle multipolygons in order to to make this blog too complicated.

Testing it we get:

``` WITH testGeom AS (
SELECT mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL) AS geom
FROM DUAL
)
SELECT geom.filter_rings(a.geom,0.005,2.0).Get_WKT() AS filtered_geom
FROM testGeom a;
--
FILTERED_GEOM
-----------------------------------------------------------------------------------------------------------
POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 5.0, 5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0))
--
1 ROWS selected
```

Or to filter out all inner rings…

``` WITH testGeom AS (
SELECT mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',NULL) AS geom
FROM DUAL
)
SELECT geom.filter_rings(a.geom,0.005,10.0).Get_WKT() AS filtered_geom
FROM testGeom a;
--
FILTERED_GEOM
-----------------------------------------------------------
POLYGON ((0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0, 0.0 0.0))
--
1 ROWS selected
```

Which are the answers we are after.

Non-SQL, Locator Friendly Version

Two problems with the above function arise:

• SQL inside functions is slower than native PL/SQL.
• The above uses SDO_GEOM.SDO_DIFFERENCE which is an advanced Spatial function requiring expensive licensing. (Though one could use my SC4O.ST_Difference function.)

So, can we create a function that is free of SQL and could be run on Locator?

Yes, here is how we can do this:

``` /** ----------------------------------------------------------------------------------------
* @function  : Filter_Rings
* @precis     : Function that allows a user to remove inner rings from a polygon/multipolygon
*               based on an area value.
* @version    : 1.0
* @usage      : FUNCTION Filter_Rings(p_geometry   in mdsys.sdo_geometry,
*                                     p_tolerance in number,
*                                     p_area      in number,
*                                     p_ring      in number := 0)
*                 RETURN mdsys.sdo_geometry DETERMINISTIC;
*               eg select Filter_Rings(mdsys.sdo_geometry('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',null),
*                                      10,
*                                      0.05)
*                    from dual;
* @param      : p_geometry  : mdsys.sdo_geometry : Original polygon/multipolygon
* @param      : p_area      : number : Area in square srid units below which an inner ring is removed.
* @param      : p_tolerance : number : Standard Oracle diminfo tolerance.
* @Param      : p_ring      : number : The number of the internal ring to be removed
* @return    : mdsys.sdo_geometry : input geometry with any qualifying inner rings removed
* @history    : Simon Greener - December 2008 - Original coding.
**/
FUNCTION Filter_Rings(p_geometry  IN mdsys.sdo_geometry,
p_tolerance IN NUMBER,
p_area      IN NUMBER,
p_ring      IN NUMBER := 0)
RETURN MDSYS.SDO_GEOMETRY
IS
v_vertices         mdsys.vertex_set_type;
v_ords             mdsys.sdo_ordinate_array := NEW mdsys.sdo_ordinate_array(NULL);
v_num_dims         pls_integer;
v_num_elems        pls_integer;
v_actual_etype     pls_integer;
v_ring_elem_count  pls_integer := 1;
v_ring             mdsys.sdo_geometry;
v_num_rings        pls_integer;
v_geom mdsys.sdo_geometry;
v_ok   NUMBER;
BEGIN
If ( p_geometry IS NULL OR MOD(p_geometry.sdo_gtype,10) NOT IN (3,7) ) THEN
raise_application_error(-20001,'p_geometry is null or is not a polygon',TRUE);
End If;
If ( DBMS_DB_VERSION.VERSION <= 10 ) THEN
Begin
-- Can only use this if Enterprise Edition and SDO
SELECT 1
INTO v_Ok
FROM v\$version
WHERE banner LIKE '%Enterprise Edition%';
SELECT 1
INTO v_Ok
FROM dba_registry
WHERE comp_id = 'SDO';
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'Not licensed for use of sdo_area',TRUE);
RETURN p_geometry;
End;
End If;
/* The processing below assumes the structure of a polygon/multipolygon
is correct and passes sdo_geom.validate_geometry */
v_num_dims  := p_geometry.get_dims();
v_num_elems := MDSYS.SDO_UTIL.GETNUMELEM(p_geometry); -- Gets number of 1003 geometries
<<all_elements>>
FOR v_elem_no IN 1..v_num_elems LOOP
-- Need to process and check all inner rings
--
-- Process all rings in the extracted single - 2003 - polygon
v_num_rings := &&defaultSchema..GEOM.GetNumRings(MDSYS.SDO_UTIL.EXTRACT(p_geometry,v_elem_no),0);
<<All_Rings>>
FOR v_ring_no IN 1..v_num_rings LOOP
v_ring := MDSYS.SDO_UTIL.EXTRACT(p_geometry,v_elem_no,v_ring_no);
IF ( v_ring IS NOT NULL ) THEN
If ( v_ring_no = 1 ) THEN -- outer ring
v_geom := CASE WHEN ( v_geom IS NULL ) THEN v_ring ELSE mdsys.sdo_util.APPEND(v_geom,v_ring) END;
Else -- Inner Ring
IF ( mdsys.sdo_geom.sdo_area(v_ring,p_tolerance) > p_area ) THEN
IF ( v_ring.sdo_ordinates.COUNT <> v_num_dims * 2 ) THEN -- If optimized rectangle don't swap
v_ring := &&defaultSchema..GEOM.Reverse_Geometry(v_ring);
End If;
v_ring.sdo_elem_info(2) := v_ring.sdo_elem_info(2) + 1000;
v_geom := mdsys.sdo_util.APPEND(v_geom,v_ring);
End If;
End If;
END IF;
END LOOP All_Rings;
END LOOP all_elements;
RETURN v_geom;
END Filter_Rings;
```

NOTE: This function requires my GEOM package to be able to compile and run: mdsys.SDO_GEOM.SDO_AREA is only an issue for versions before 10g.

Testing we get:

``` SELECT id, filtered_geom
FROM (
SELECT 1 AS id,filter_rings(a.mgeom,0.005,2.0)   AS filtered_geom FROM testGeoms a UNION ALL
SELECT 2 AS id,filter_rings(a.mgeom,0.005,200.0) AS filtered_geom FROM testGeoms a UNION ALL
SELECT 3 AS id,filter_rings(a.geom,0.005,2.0)    AS filtered_geom FROM testGeoms a UNION ALL
SELECT 4 AS id,filter_rings(a.geom,0.005,10.0)   AS filtered_geom FROM testGeoms a UNION ALL
SELECT 5 AS id,filter_rings(a.cgeom,0.005,2.0)   AS filtered_geom FROM testGeoms a UNION ALL
SELECT 6 AS id,filter_rings(a.cmgeom,0.005,2.0)  AS filtered_geom FROM testGeoms a UNION ALL
SELECT 7 AS id,filter_rings(a.twosq,0.005,3.0)   AS filtered_geom FROM testGeoms a
)
ORDER BY id;
-- Results
ID FILTERED_GEOM
-- ---------------------------------------------------------------------------------------------------------------------
1  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1,11,2003,1,21,1003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0,5,5,5,7,7,7,7,5,5,5,40,40,50,40,50,50,40,50,40,40))
2  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1,11,1003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0,40,40,50,40,50,50,40,50,40,40))
3  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1,11,2003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0,5,5,5,7,7,7,7,5,5,5))
4  SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0))
5  SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,1,5,2,2),SDO_ORDINATE_ARRAY(6,10,10,1,14,10,10,14,6,10))
6  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,1,5,2,2,11,2005,2,11,2,2,15,2,1,21,1005,2,21,2,1,25,2,2),SDO_ORDINATE_ARRAY(6,10,10,1,14,10,10,14,6,10,13,10,10,2,7,10,10,13,13,10,106,110,110,101,114,110,110,114,106,110))
7  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,3,5,1003,1,15,2003,3),SDO_ORDINATE_ARRAY(-10,-10,60,60,0,0,50,0,50,50,0,50,0,0,10,10,12,12))
--
7 ROWS selected
```

There is an issue with the third resulting polygon as the original polygon was 2003. I will see if I can track down why this is occurring.

I hope this helpful to someone.