选择与表值函数连接中的 Oracle 标量函数

Posted

技术标签:

【中文标题】选择与表值函数连接中的 Oracle 标量函数【英文标题】:Oracle Scalar function in select vs table valued function join 【发布时间】:2011-07-20 20:51:22 【问题描述】:

我有一个表现不佳的查询。查询的一个方面是在表值函数上使用交叉连接,老实说,我是在模仿我的 TSQL 行为,即在函数上使用 CROSS APPLY 以避免使用标量函数调用。这是 Oracle 中的不良行为吗?

我遇到的主要问题是 Oracle Tuning Advisor 不会解析我的查询,因此我还无法研究索引优化。通常我不会发布这么多代码,但我怀疑是我的查询而不是表优化可能导致问题。

statistics se 表确实是唯一一个超过 4,000,000 条记录的表。任何人都可以建议删除公然不良的 Oracle 行为吗?或者,如果一切看起来都很好,是获得一些索引调整建议的好工具? Oracle Enterprise Manager 不会解析此查询以提供任何建议。

从跟踪中捕获并格式化的其他性能信息 通过 TKPROF

解析:Count(1) | CPU(0.04) |经过(0.04) |磁盘(0) |查询(852) |当前(0) |行(0)

执行:Count(1) | CPU(0.00) |已用(0.00) |磁盘(0) |查询(0) |当前(0) |行(0)

获取:Count(1) | CPU(9.64) |已过(14.50) |磁盘(34578) |查询(35610) |当前(4) |行(4)

解析期间库缓存中的未命中数:1 优化器模式:ALL_ROWS 解析用户id:1165

Rows行源操作


  4  HASH JOIN OUTER (cr=38069 pr=34578 pw=0 time=19208475 us)
  2   COLLECTION ITERATOR PICKLER FETCH REPORT_INTERVAL_SEQUENCE_UDF (cr=97 pr=0 >                                                                    pw=0 time=13766 us)
  4   VIEW  (cr=37972 pr=34578 pw=0 time=19194353 us)
  4    HASH GROUP BY (cr=37972 pr=34578 pw=0 time=19194329 us)  

60650 过滤器(cr=37972 pr=34578 pw=0 time=19673947 us) 60650 个嵌套循环(cr=37972 pr=34578 pw=0 time=19431329 us) 60650 哈希联接(cr=37941 pr=34578 pw=0 time=5294908 us) 4 COLLECTION ITERATOR PICKLER FETCH REPORT_MACHINEINFO_GETT_UDF (cr=2331 pr=0 pw=0 time=212033 us) 60650 表访问完整 ELS_STATISTIC_ENTRY (cr=35610 pr=34578 pw=0 时间=4416705 我们) 60650 COLLECTION ITERATOR PICKLER FETCH REPORT_INTERVAL_GETT_UDF (cr=31 pr=0 > pw=0 time=13372794 us)

SELECT
         TimeInterval,
         stats.During,
         stats.Name,
         stats.cnt
    FROM
        TABLE (GET_INTERVAL_SEQUENCE_UDF(
                                         TO_TIMESTAMP ('07/15/2011','mm/dd/yyyy')
                                        ,TO_TIMESTAMP ('07/20/2011','mm/dd/yyyy')
                                        ,2)) dtRange
    LEFT JOIN
    (
         SELECT
              i.During
              , mi.Name
              , SUM (CAST (VALUE_NUMERIC AS INT)) cnt

         FROM
              statistics se
         JOIN TABLE (Get_Context_Info_udf ()) mi 
              ON (se.Context_ID = mi.Context_ID)
         CROSS JOIN TABLE (Interval_GetT (se.EntryDate, 2)) i
         WHERE
              StatisticTypeID = HEXTORAW ('6CF933B091AE46FEA7F56BE96308190F') 
              AND EntryDate < TO_TIMESTAMP ('07/20/2011','mm/dd/yyyy') 
              AND EntryDate > TO_TIMESTAMP ('07/15/2011', 'mm/dd/yyyy')
         GROUP BY
             i.During
             , mi.Name
    ) stats ON dtRange.TimeInterval = stats.TimeInterval


The following are for reference in the aforementioned query.


CREATE OR REPLACE FUNCTION Interval_GetT(datestamp IN timestamp,  timeInterval IN int) 
RETURN TReportIntervalList AS vResult TReportIntervalList;
BEGIN
     SELECT TReportInterval(
                            CASE timeInterval 
                            WHEN 1 THEN TO_CHAR(datestamp, 'YYYY-MM-DD HH24') 
                            WHEN 2 THEN TO_CHAR(datestamp, 'YYYY-MM-DD')
                            WHEN 3 THEN TO_CHAR(datestamp, 'YYYY-WW')
                            END
                           ) 
     BULK COLLECT INTO vResult                                       
     FROM Dual WHERE ROWNUM = 1;

     RETURN vResult;
END;



CREATE OR REPLACE FUNCTION GET_INTERVAL_SEQUENCE_UDF(
      startTime IN timestamp,
      endTime IN timestamp,
      inputInterval IN int)
      RETURN t_interval_list_table   AS  intervalList t_interval_list_table := t_interval_list_table();
    BEGIN

    SELECT 
         CASE inputInterval
         WHEN 1 THEN (t_interval(REPORT_Interval_Get_udf((startTime + ((ROWNUM-1) * 1/24)), inputInterval))) --Hour
         WHEN 2 THEN (t_interval(REPORT_Interval_Get_udf((startTime + (ROWNUM-1)), inputInterval))) --Day
         WHEN 3 THEN (t_interval(REPORT_Interval_Get_udf((startTime + ((ROWNUM-1)*7)), inputInterval))) --Week
            END 
          BULK COLLECT INTO intervalList
          FROM dual CONNECT BY LEVEL <= (CASE inputInterval 
                                          WHEN 1 THEN CAST(CEIL(((TRUNC(endTime, 'HH') - TRUNC(startTime, 'HH')) * 24)) AS INT)
                                          WHEN 2 THEN CAST(TRUNC(endTime, 'DD') - TRUNC(startTime, 'DD') AS INT)
                                          WHEN 3 THEN CAST(CEIL(((TRUNC(endTime, 'DD') - TRUNC(startTime, 'DD')) )/7) AS INT)
                                       END);
      RETURN intervalList; 

    END GET_INTERVAL_SEQUENCE_UDF;


CREATE OR REPLACE FUNCTION      Get_Context_Info_udf
    RETURN TTRFRMENGMACHINEINFOLIST AS vResult TTRFRMENGMACHINEINFOLIST;
    BEGIN    
        SELECT TTrfrmEngMachineInfo(ch.Context_ID, mac.Name)
        BULK COLLECT INTO vResult   
        FROM
            a ch  
        INNER JOIN
            b cxm  ON ch.CONTX_MACHINE_ID = cxm.CONTX_MACHINE_ID   
        INNER JOIN
            c mac ON cxm.MACHINE_ID = mac.MACHINE_ID   
        INNER JOIN
            d ic  ON mac.MACHINE_ID = ic.MACHINE_ID  
        WHERE 
            ic.ONFIGURABLE_ENTITY_ID =  HEXTORAW(Format_Guid_udf('11111111-FAE9-47A1-91A9-60A53E9660FE'))
            AND mac.IS_DELETED = 'N'
            AND ic.IS_DELETED = 'N';

        RETURN vResult; 
     END;

【问题讨论】:

下一次,为了帮助人们解析您的问题,请使用段落和正确的标点符号。它可以帮助我们的眼睛更快地索引文本。并选择您发布的整个代码块,然后点击编辑器中的 按钮使其更漂亮。 tkprof 为您的集合和函数提到了其他名称,而不是您在此处的其他帖子。我不知道您使用哪种工具来生成此输出,但就目前而言,它几乎不可读。请使用 tkprof 的直接复制粘贴。此外,您的 tkprof 文件中的其他查询似乎非常相关。如果您使用 tkprof 的排序参数,您可以将它们放在文件的顶部。 【参考方案1】:

您可以按照this OTN thread 中的建议调查时间花在哪里以及 Oracle 选择的执行计划

问候, 抢。

【讨论】:

【参考方案2】:

这一切对我来说都是陌生的:)

首先,SELECT FROM DUAL 在 PL/SQL 中是不常见的。

CREATE OR REPLACE FUNCTION Interval_GetT(datestamp IN timestamp,  timeInterval IN int) 
RETURN TReportIntervalList AS vResult TReportIntervalList;
BEGIN
     SELECT TReportInterval(
              CASE timeInterval 
                     WHEN 1 THEN TO_CHAR(datestamp, 'YYYY-MM-DD HH24') 
                     WHEN 2 THEN TO_CHAR(datestamp, 'YYYY-MM-DD')
                     WHEN 3 THEN TO_CHAR(datestamp, 'YYYY-WW')
              END) 
     BULK COLLECT INTO vResult                                       
     FROM Dual WHERE ROWNUM = 1;

     RETURN vResult;
END;

会更简单

CREATE OR REPLACE FUNCTION Interval_GetT(datestamp IN timestamp,  timeInterval IN int) 
RETURN TReportIntervalList;
BEGIN
  IF timeInterval  = 1 THEN
       RETURN TReportInterval(TO_CHAR(datestamp, 'YYYY-MM-DD HH24'));
  ELSIF timeInterval  = 2 THEN
       RETURN TReportInterval(TO_CHAR(datestamp, 'YYYY-MM-DD'));
  ELSIF timeInterval  = 3 THEN
       RETURN TReportInterval(TO_CHAR(datestamp, 'YYYY-WW'));
  ELSE
       RETURN NULL;
  END IF;
END;

不知道 TReportInterval 做了什么,所以很难知道那个模块做了什么。 我会看一个 PIPELINED PL/SQL 函数来替换 GET_INTERVAL_SEQUENCE_UDF。您将面临这样的困难是优化器永远不会知道它将返回多少行,因此它经常会猜错。

Get_Context_Info_udf 也会出现类似的问题。没有明显的迹象表明它会返回 1 行还是 10,000 行。同样,TTrfrmEngMachineInfo 完全不透明。

坦率地说,正在尽一切可能使优化器不知道如何最好地执行查询。

如果统计表是主要的,我认为你是基于过滤表

 WHERE
      StatisticTypeID = HEXTORAW ('6CF933B091AE46FEA7F56BE96308190F') 
      AND EntryDate < TO_TIMESTAMP ('07/20/2011','mm/dd/yyyy') 
      AND EntryDate > TO_TIMESTAMP ('07/15/2011', 'mm/dd/yyyy')

根据 Context_ID 汇总 value_numeric。

可能是某个日期维度的某种摘要(可能是每天/每周/每月总计?)

我会尝试尽可能多地摆脱 PL/SQL。从针对统计数据的简单查询开始,并描述您在每个阶段要执行的操作。

【讨论】:

TReportInterval 是为 TReportIntervalList 定义的类型,这就是为什么我使用 case 而不是 IF 进行选择的原因...您提供的语句将无法编译,因为返回类型与函数不匹配类型。使用用户定义类型的重点是能够将其用作 CROSS APPLY (TSQL),然后能够对其进行分组以完成统计汇总。

以上是关于选择与表值函数连接中的 Oracle 标量函数的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server - 将标量函数转换为表值函数

在多标量表值函数中调用存储过程?

sql2005中 表值函数是啥

使用带有标量函数的 where 条件与使用交叉应用和表值函数的 where 条件

SQL 自定义函数(Function)——参数默认值

内联表值函数中的 IF Else