The Question:
I have a spatial table (road lines), stored using ESRI's SDE.ST_GEOMETRY
user-defined datatype in an Oracle 12c geodatabase. I want to list the line vertices so that I can ultimately access & update their coordinates. If I was using SDO_GEOMETRY/Oracle Locator, then I would use the
SDO_UTIL.GETVERTICES
function. But I'm not using SDO_GEOMETRY/Oracle Locator, and there is no equivalent function in SDE.ST_GEOMETRY
. The only SDE.ST_GEOMETRY
functions I can find that pertain to vertices are ST_PointN
and ST_NumPoints
.
I've come up with a query that successfully does all this - gets the line vertices as rows (inspired by this page):
1 SELECT a.ROAD_ID
2 ,b.NUMBERS VERTEX_INDEX
3 ,a.SDE.ST_X(SDE.ST_PointN(a.SHAPE, b.NUMBERS)) AS X
4 ,a.SDE.ST_Y(SDE.ST_PointN(a.SHAPE, b.NUMBERS)) AS Y
5 FROM ENG.ROADS a
6 CROSS JOIN ENG.NUMBERS b
7 WHERE b.NUMBERS <= SDE.ST_NumPoints(a.SHAPE)
8 --removed to do explain plan: ORDER BY ROAD_ID, b.NUMBERS
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 1 | MERGE JOIN | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 2 | INDEX FULL SCAN | R23715_SDE_ROWID_UK | 30 | 90 | | 1 (0)| 00:00:01 |
|* 3 | SORT JOIN | | 3997 | 1018K| 2392K| 261 (1)| 00:00:01 |
| 4 | TABLE ACCESS FULL| ROAD | 3997 | 1018K| | 34 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 3 - access(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
" filter(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
It CROSS JOINS
the lines in the ROADS
table to a NUMBERS
table (and limits the results to the number of vertices in each line).
Stats: (updated)
- Each line has a maximum of 30 vertices (average of 4.38 vertices per line)
- ROADS has 3,997 lines
- NUMBERS has 30 rows (sequential numbers starting at 1)
- The result set has 17,536 rows
However, the performance is poor (40 seconds), and I can't help but think - is there a more elegant way to do this? To me, using a numbers table and a cross join seems like a sloppy approach. Is there a better way?
Layman's terms would be appreciated; I'm a Public Works guy, not a DBA.
Update #1:
If I remove lines 3 & 4 (string of X & Y related functions) from the query, it executes instantly. But of course, I can't just remove these lines, I need the X & Y columns. So this leads me to believe that the slow performance has something to do with the X & Y functions.
However, if I export the points to a static table, and then run the X & Y functions on it, this executes instantly as well.
So, does this mean that the slow performance is caused by the X & Y functions, except, well, no it isn't? I'm confused.
Update #2:
If I bring the X and Y out of the query, put them in an outer query, and add ROWNUM to the inner query, then it's much quicker (16 seconds - updated):
SELECT
ROWNUM
,ROAD_ID
,VERTEX_INDEX
,SDE.ST_X(ST_POINT) AS X
,SDE.ST_Y(ST_POINT) AS Y
FROM
(
SELECT
ROWNUM
,a.ROAD_ID
,b.NUMBERS VERTEX_INDEX
,SDE.ST_PointN(a.SHAPE, b.NUMBERS) AS ST_POINT
FROM ENG.ROAD a
CROSS JOIN ENG.NUMBERS b
WHERE b.NUMBERS <= SDE.ST_NumPoints(a.SHAPE)
)
--removed to do explain plan: ORDER BY ROAD_ID, VERTEX_INDEX
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5996 | 322K| | 262 (1)| 00:00:01 |
| 1 | COUNT | | | | | | |
| 2 | VIEW | | 5996 | 322K| | 262 (1)| 00:00:01 |
| 3 | COUNT | | | | | | |
| 4 | MERGE JOIN | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 5 | INDEX FULL SCAN | R23715_SDE_ROWID_UK | 30 | 90 | | 1 (0)| 00:00:01 |
|* 6 | SORT JOIN | | 3997 | 1018K| 2392K| 261 (1)| 00:00:01 |
| 7 | TABLE ACCESS FULL| ROAD | 3997 | 1018K| | 34 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 6 - access(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
" filter(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
Justin Cave explains why ROWNUM helps performance here: Why does adding ROWNUM to a query improve performance?
While this performance improvement is good, it's not yet good enough. And I can't help but think that I still don't fully understand how the query works or why it is as slow as it is.
The question still stands: is there a better way?