在数字表上交叉连接以获得线顶点,是否有更好的方法?


8

问题:

我有一个空间表(路线),使用ESRI的SDE.ST_GEOMETRY用户定义数据类型存储在Oracle 12c 地理数据库中。我想列出线顶点,以便最终访问并更新它们的坐标。如果使用的是SDO_GEOMETRY / Oracle Locator,则可以使用该 SDO_UTIL.GETVERTICES功能。但是我没有使用SDO_GEOMETRY / Oracle Locator,并且中没有等效功能SDE.ST_GEOMETRY。我可以找到的与顶点有关的唯一SDE.ST_GEOMETRY 函数ST_PointNST_NumPoints

我想出一个可以成功完成所有操作的查询-将行顶点作为行(受此页面的启发):

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""))"

它将表中CROSS JOINS的行限制ROADS为一个NUMBERS表(并将结果限制为每行中的顶点数)。

统计:(已更新)

  • 每行最多有30个顶点(每行平均4.38个顶点)
  • ROADS有3,997条线
  • NUMBERS有30行(序号从1开始)
  • 结果集有17,536行

但是,性能很差(40秒),我不禁想到-有没有更优雅的方法呢?对我来说,使用数字表和交叉联接似乎是一种草率的方法。有没有更好的办法?

外行的条款将不胜感激;我是公共工程人员,不是DBA。


更新#1:

如果我从查询中删除第3行和第4行(与X和Y相关的函数的字符串),它将立即执行。但是,当然,我不能只删除这些行,我需要 X&Y列。因此,这使我相信,性能下降与X&Y函数有关。

但是,如果将点导出到静态表中,然后在其上运行X&Y函数,则该操作也会立即执行。

那么,这是否意味着性能下降是由X&Y函数引起的,除了,不是吗?我糊涂了。


更新#2:

如果我将X和Y移出查询,将它们放在外部查询中,然后将ROWNUM添加到内部查询中,则速度会更快(16秒-更新):

    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)在这里解释了为什么ROWNUM有助于提高性能:为什么在查询中添加ROWNUM可以提高性能?

尽管此性能改进不错,但还不够好。而且我不禁以为我仍然不完全了解查询的工作方式或查询速度如此缓慢的原因。

问题仍然存在:是否有更好的方法?


评论不作进一步讨论;此对话已转移至聊天
保罗怀特9

Answers:


7

我对Oracle性能有点了解,而对自定义数据类型却一无所知,但我会尽力为您提供一个提高性能的计划。

1)确认您无法获得解释计划。

即使您没有复杂的数据库软件,也可以得到解释计划。如果执行,会发生什么set autotrace on explain怎么办?

您也可以尝试DBMS_XPLAN。首先,通过在查询中添加一些其他关键字来节省计划:

explain plan for (SELECT... your query goes here); 

然后执行以下命令:

SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());

这些可能都不起作用,而您实际上无法获得解释计划。我只是想验证一下,因为有了解释计划,社区对您的帮助会更加容易。

2)考虑需求。

您说20秒不够好。您或其他人确切定义了什么足够好吗?有谈判的余地吗?您的查询是否需要完全是一个SELECT查询?您可以一步填充全局临时表,然后在下一步中选择所需的结果吗?您能否创建一个存储过程来返回结果集并调用它?

3)确定完成查询所需时间的下限。

我建议运行一个简单的“欺骗”查询,以找出优化后的查询的外观。例如,仅获取第一个顶点的查询需要花费多长时间?

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
          ,1 VERTEX_INDEX
          ,SDE.ST_PointN(a.SHAPE, 1) AS ST_POINT
    FROM  ENG.ROAD a
)
ORDER BY ROAD_ID, VERTEX_INDEX;

我怀疑这会给你4000行。如果将该查询的响应时间乘以17.5 / 4,则可以为总执行时间提供一个很好的下限。

如果您的总执行时间下限比在第2步中确定的时间长,那么您要么需要通过提前计算结果并将其存储在表中来使自己的数据模型更具创造力,要么需要重新协商所需的响应时间。

4)确定哪些功能对您的执行时间贡献最大。

您在使用更新#1时走在正确的轨道上,但是您需要尝试控制要完成的工作量。例如,是否可以编写一组相对简单的查询,这些查询恰好执行10000次?响应时间如何比较?

5)去上班。

根据在步骤2中建立的要求和在步骤4中发现的内容,尝试可以想到的任何技巧来减少查询运行时间。您是否可以预先计算结果并保存下来?如果问题与执行功能的次数有关,则未记录的具体化提示可能会有所帮助。这迫使Oracle在幕后创建一个隐藏的临时表来存储结果。我不知道它是否与您使用的特殊数据类型兼容。

例如,也许这样的效果更好?抱歉,如果无法编译,但我无法测试。

WITH ROAD_CTE (ROAD_ID, VERTEX_INDEX, SHAPE) AS
(
    SELECT /*+ materalize */
      a.ROAD_ID
    , b.NUMBERS VERTEX_INDEX
    , a.SHAPE
    FROM ENG.ROAD a
    CROSS JOIN ENG.NUMBERS b
    WHERE b.NUMBERS <= SDE.ST_NUMPOINTS(a.SHAPE)
)
, CTE_WITH_ST_POINT (ROAD_ID, VERTEX_INDEX, ST_POINT) AS
(
    SELECT /*+ materalize */
      rcte.ROAD_ID
    , rcte.VERTEX_INDEX
    , SDE.ST_PointN(rcte.SHAPE, rcte.VERTEX_INDEX) ST_POINT
    FROM ROAD_CTE rcte
)
SELECT 
      ROAD_ID
    , VERTEX_INDEX
    , SDE.ST_X(ST_POINT) AS X
    , SDE.ST_Y(ST_POINT) AS Y
FROM CTE_WITH_ST_POINT
ORDER BY ROAD_ID, VERTEX_INDEX;

如果您仍然无法解决所有问题,我怀疑它至少会为您提供其他信息,您可以在问题中进行编辑。祝好运!


2

我尝试使用CONNECT BY(和DUAL)来查看它是否更快,但事实并非如此(大致相同)。

SELECT  ROAD_ID
        ,T.VERTEX_INDEX
        ,SDE.ST_X(SDE.ST_PointN(SHAPE, T.VERTEX_INDEX)) AS X
        ,SDE.ST_Y(SDE.ST_PointN(SHAPE, T.VERTEX_INDEX)) AS Y
FROM    ENG.ROADS 
        CROSS JOIN
            (
            SELECT LEVEL AS VERTEX_INDEX 
            FROM DUAL CONNECT BY LEVEL <= 
                (
                SELECT MAX(SDE.ST_NUMPOINTS(SHAPE)) 
                FROM ENG.ROADS 
                )
            ) T
WHERE    T.VERTEX_INDEX <= SDE.ST_NUMPOINTS(SHAPE)
--removed to do explain plan: ORDER BY ROAD_ID, VERTEX_INDEX

-------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name                 | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                      |   200 | 54800 |    36   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                  |                      |   200 | 54800 |    36   (0)| 00:00:01 |
|   2 |   VIEW                         |                      |     1 |    13 |     2   (0)| 00:00:01 |
|*  3 |    CONNECT BY WITHOUT FILTERING|                      |       |       |            |          |
|   4 |     FAST DUAL                  |                      |     1 |       |     2   (0)| 00:00:01 |
|   5 |     SORT AGGREGATE             |                      |     1 |   261 |            |          |
|   6 |      TABLE ACCESS FULL         | ROAD                 |  3997 |  1018K|    34   (0)| 00:00:01 |
|*  7 |   TABLE ACCESS FULL            | ROAD                 |   200 | 52200 |    34   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
"   3 - filter(LEVEL<= (SELECT MAX(""SDE"".""ST_NUMPOINTS""(""SHAPE"")) FROM "
"              ""ENG"".""ROAD"" ""ROAD""))"
"   7 - filter(""T"".""VERTEX_INDEX""<=""SDE"".""ST_NUMPOINTS""(""ROAD"".""SHAPE""))"

我从这篇文章中得到了一个主意:如何在Oracle中计算范围?


2

结果和对Joe Obbish的回答

注意:从这里开始,我将把更新2中的查询称为“查询”。我不会在原始问题中引用查询。

1)确认您无法获得解释计划。

我无法执行set autotrace on explain。我收到此错误:ORA-00922: missing or invalid option (#922)

但我能执行DBMS_XPLAN。我以为我将无法做到这一点。幸运的是,我错了。我现在正在运行解释计划。

2)考虑需求。

您的查询是否需要完全是一个SELECT查询?

我认为查询确实需要完全是一个查询。我使用的软件非常有限,并且不允许使用多个select语句。

您是否明确定义了您的要求?

  • 对线几何进行编辑后,该查询将用于更新顶点坐标。通常,这一次可能会同时发生在一条线路上,或者可能会发生在几十条线路上,但可能不会发生在数千条线路上。在这种情况下,查询的当前性能将足够。
  • 该查询还将用于为所有3,805条线构造新的线几何(这与动态分段/ 线性参考的主题有关)。这将在视图中即时发生,因此性能绝对至关重要。查询可能需要在不到5秒的时间内执行。

3)确定完成查询所需时间的下限。

第一个顶点查询在3.75秒内执行(按预期返回3805行)。

3.75 sec * (16495 total / 3805 lines) = 16.25 sec

结果:总执行时间的下限比我在步骤2中建立的下限更长(5秒)。因此,我认为解决方案是“ ...通过提前计算结果并将其存储在表中来获得我的数据模型的创造力”(所需的响应时间不可协商)。换句话说,进行物化视图。

此外,下限16.25秒与更新#2中查询的总执行时间(16秒)匹配。我认为,鉴于我必须使用的功能和数据,这证明我的查询已得到完全优化。

4)确定哪些功能对您的执行时间贡献最大。

我创建了两个表(均包含10,000行):ROADS_BMROADS_STARTPOINT_BM。我已经使用所涉及的每个功能在表上运行了简单的查询。结果如下:

               +-----------+------------------+---------------------------------------------------------------------------+
               | TIME(sec) | RETURN TYPE      | QUERY                                                                     |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_X         | < 0.5     | Double precision | SELECT ROAD_ID FROM (                                                     |
|              |           | (Number)         | SELECT ROAD_ID, SDE.ST_X(SHAPE) AS X FROM ENG.ROADS_STARTPOINT_BM         |
|              |           |                  | ) WHERE X IS NOT NULL ORDER BY ROAD_ID                                    |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_Y         | < 0.5     | Double precision | SELECT ROAD_ID FROM (                                                     |
|              |           | (Number)         | SELECT ROAD_ID, SDE.ST_Y(SHAPE) AS Y FROM ENG.ROADS_STARTPOINT_BM         |
|              |           |                  | ) WHERE Y IS NOT NULL ORDER BY ROAD_ID                                    |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_NumPoints | < 0.5     | Integer          | SELECT ROAD_ID FROM (                                                     |
|              |           |                  | SELECT ROAD_ID, SDE.ST_NumPoints(SHAPE) AS NUM_POINTS FROM ENG.ROADS_BM   |
|              |           |                  | ) WHERE NUM_POINTS IS NOT NULL ORDER BY ROAD_ID                           |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_PointN*   | **9.5**   | ST_POINT         | SELECT ROAD_ID FROM (                                                     |
|              |           | (ST_GEOMETRY     | SELECT ROAD_ID, SDE.ST_PointN(SHAPE,1) AS ST_POINT FROM ENG.ROADS_BM      |
|              |           | subclass)        | ) WHERE ST_POINT IS NOT NULL ORDER BY ROAD_ID                             |
+--------------+-----------+------------------+---------------------------------------------------------------------------+

功能文档:ST_XST_YST_NumPointsST_PointN

结果?ST_PointN是问题。与其他功能相比,它的9.5秒响应时间令人震惊。我想这有点道理ST_PointN返回ST_POINT几何数据类型,与其他返回简单数字的函数相比,它必须相当复杂。

注意:ST_PointN很棘手。它的返回类型是ST_POINT,我的软件不知道该如何处理结果集中的: ORA-24359: OCIDefineObject not invoked for a Object type or Reference

为了解决这个问题,我将其放在内联查询中,以防止将该列返回到结果集中。但是当我这样做时,查询实际上并没有处理该列,这违背了测试的目的。因此,我检查外部查询中是否为null WHERE ST_POINT IS NOT NULL ORDER BY RDSEC。通过这样做,我确保该 ST_PointN函数实际上正在使用,而没有返回给结果集。

当然,我想进行一次苹果对苹果的测试,因此我也对其他功能进行了相同类型的内联查询(即使在技术上没有必要)。

5)去上班。

根据步骤2、3和4,这是我的发现:

  • 问题是ST_PointN功能。太慢了 我认为我对此无能为力。除了尝试完全重新编程/重新创建功能之外,希望我可以做得比做它的专家还要好。不完全实用。
  • 为了获得所需的性能,我需要在表或实例化视图中预先计算查询。
  • 至于“ ..tricks,您可以考虑减少查询运行时间”,我也许可以消除较长行中的某些顶点。这将允许我从NUMBERS表中删除几行(目前有30行)。这样可以加快连接速度(尽管性能的提高很小)。尽管我的性能问题与索引/联接无关,但我也应该查看所有表索引。
  • 根据测试,我不认为问题“ ...与功能执行的次数有关”。
  • #5中提供的CTE查询编译得很好(我印象深刻的是Joe能够做到这一点)。但是令人惊讶的是,执行时间为30秒,这并不是一个改进。我想ST_PointN也应该为此负责。不过,CTE查询并不是浪费;通过使用它,我学到了很多东西。

六,结论。

我对我已尽可能优化查询感到满意。我将设置预计算,然后继续下一步。非常感谢Joe Obbish;我从他提供的步骤中学到了很多东西。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.