创建自定义的多参数Oracle分析函数

时间:2015-08-25 18:11:45

标签: oracle plsql oracle11g customization analytic-functions

背景

我知道在Oracle中,可以创建处理值集合并返回单个结果的custom aggregate functions修改我甚至在docs.oracle.com/cd/B28359_01/appdev.111/b28425/aggr_functions.htm阅读了友好的手册!

我也知道Oracle提供了built-in analytic functions,如DENSE_RANKRATIO_TO_REPORT,它们为每个输入提供相对于输入值的集合/窗口的值。

问题

我想知道的是,是否有创建我自己的分析函数的方法,可能与我创建自己的聚合函数的方式相似,特别是创建一个<我的自定义分析函数中的strong>附加参数。

微妙的术语警告

当我参考&#34;解析函数&#34;时,请将其作为一个函数读取,除了通过PARTITION关键字接受窗口参数外,还可以在给定窗口内返回不同的值。 (如果有人有更好的术语,请告诉我!纯分析函数?DENSE_RANK - 类分析函数?非聚合分析函数?)

Oracle文档指出聚合函数可用作分析(窗口)函数。不幸的是,这仅意味着用于在分析函数中指定窗口的PARTITION关键字也可以应用于聚合函数。它并没有将聚合函数提升到我能够在固定窗口内返回不同值的令人垂涎的状态。

用作分析的聚合:

SELECT SUM(income) OVER (PARTITION BY first_initial) AS total FROM data;

将包含与data一样多的记录,但它只有与第一个首字母一样多的total个。

用作分析的分析:

SELECT RATIO_TO_REPORT(income) OVER (PARTITION BY first_initial) AS ratio FROM data;

将包含与data,AND一样多的记录,即使在给定的first_initial分区中,这些ratio也可能都是不同的。

上下文

我获得了对PL / SQL过程的只调用访问权限,该过程接受数字集合作为IN OUT参数,并且具有一些其他IN配置参数。该程序以受配置参数影响的方式修改集合的值(将其视为&#34;大学的专有制裁和所需等级弯曲程序&#34;)。

目前,使用该过程的过程是硬编码检测从一个数据分区到另一个数据分区的变化的游标循环,然后在每个分区内将数据提取到一个集合中,然后将该集合传递给过程,更改并最终倾倒回另一张桌子。我计划通过制作一个封装了一些逻辑的PIPELINED PARALLEL_ENABLE表函数来改进这一点,但我更喜欢启用如下查询:

SELECT G.Course_ID
     , G.Student_ID
     , G.Raw_Grade
     , analytic_wrapper(G.raw_grade, P.course_config_data)
                  OVER (PARTITION BY G.Course_ID) AS Adjusted_Grade
     , P.course_config_data
  FROM      grades   G
  LEFT JOIN policies P
  ON G.Course_ID = P.Course_ID;

这需要能够创建自定义分析函数,并且由于过程在不同分区上需要不同输入的方式(例如上面的Course_ID - 特定P.course_config_data),它还必须不仅要接受数据聚合参数,还要接受其他输入。

这是可能的,如果可以,我在哪里可以找到文件?我的Google-fu让我失望了。

额外的皱纹

我提供的PL / SQL程序(有效地)是非确定性的,并且其输出具有必须保留的统计特性。例如,如果A={A[0], A[1], A[3]}是某个特定类的原始成绩,则B=f(A)是在{1}}在1:00调用该过程的结果,而A是结果在1:15调用C=f(A)上的过程,然后AB={B[0],B[1],B[2]}都是可接受的输出,但是C={C[0],C[1],C[2]}等元素的混合是不可接受的。

这样做的结果是必须在每个分区上将该过程称为一次。 (从技术上讲,可以根据需要浪费多次调用它,但分区的所有结果必须来自同一个调用)。

例如,假设我提供的程序操作如下:它接受一组成绩作为{C[0],B[1],C[2]}参数,然后设置其中一个等级,随机选择,到100.所有其他等级都设为零。在下午1点运行这可能会导致Alice拥有唯一的及格分数,而在下午1:01运行它可能会导致Bob获得唯一的及格分数。无论如何,应该是每个班级只有一名学生通过,不多也不少。

4 个答案:

答案 0 :(得分:2)

这个版本不会受到我之前回答的警告,虽然它会变得更慢,更难以使用。来自ODCIAggregateDelete循环的大部分缓慢 - 你可能能够在那里找到一个不需要循环整个集合的改进。

无论如何,这个版本创建了一个模仿Oracle原生COLLECT功能的自定义分析功能。因此,它不是试图创建一个计算我们想要的实际值的自定义分析函数,而是仅计算窗口中的行集。

然后,对于每一行,我们将行数据和自定义“COLLECT”分析的结果传递给计算我们想要的值的常规函数​​。

这是代码。 (注意:您的原始问题也询问了多个参数。简单 - 只需将您想要的所有字段放入matt_ratio_to_report_rec。)(另外,抱歉对象名称 - 我的名字前缀是其他开发人员知道的询问对象是否导致问题的人。)

-- This is the input data to the analytic function
--DROP TYPE matt_ratio_to_report_rec;
CREATE OR REPLACE TYPE matt_ratio_to_report_rec AS OBJECT
  ( value   NUMBER );

-- This is a collection of input data  
--DROP TYPE matt_ratio_to_report_tab;
CREATE OR REPLACE TYPE matt_ratio_to_report_tab AS TABLE OF matt_ratio_to_report_rec;


-- This object type implements a custom analytic that acts as an analytic version of Oracle's COLLECT function
--DROP TYPE matt_ratio_to_report_col_impl;
CREATE OR REPLACE TYPE matt_ratio_to_report_col_impl AS OBJECT (
  analytics_window    matt_ratio_to_report_tab,
  CONSTRUCTOR FUNCTION matt_ratio_to_report_col_impl(SELF IN OUT NOCOPY matt_ratio_to_report_col_impl ) RETURN SELF AS RESULT,  
-- Called to initialize a new aggregation context
-- For analytic functions, the aggregation context of the *previous* window is passed in, so we only need to adjust as needed instead 
-- of creating the new aggregation context from scratch
  STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_ratio_to_report_col_impl) RETURN NUMBER,
-- Called when a new data point is added to an aggregation context  
  MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_ratio_to_report_col_impl, value IN matt_ratio_to_report_rec ) RETURN NUMBER,
-- Called to return the computed aggragate from an aggregation context
  MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_ratio_to_report_col_impl, returnValue OUT matt_ratio_to_report_tab, flags IN NUMBER) RETURN NUMBER,
-- Called to merge to two aggregation contexts into one (e.g., merging results of parallel slaves) 
  MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_ratio_to_report_col_impl, ctx2 IN matt_ratio_to_report_col_impl) RETURN NUMBER,
  -- ODCIAggregateDelete
  MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_ratio_to_report_col_impl, value matt_ratio_to_report_rec) RETURN NUMBER  
);

CREATE OR REPLACE TYPE BODY matt_ratio_to_report_col_impl IS

CONSTRUCTOR FUNCTION matt_ratio_to_report_col_impl(SELF IN OUT NOCOPY matt_ratio_to_report_col_impl ) RETURN SELF AS RESULT IS
BEGIN
  SELF.analytics_window := new matt_ratio_to_report_tab();
  RETURN;
END;


STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_ratio_to_report_col_impl) RETURN NUMBER IS
BEGIN
  DBMS_OUTPUT.PUT_LINE('ODCIAggregateInitialize()');
  sctx := matt_ratio_to_report_col_impl ();
  RETURN ODCIConst.Success;
END;


MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_ratio_to_report_col_impl, value IN matt_ratio_to_report_rec ) RETURN NUMBER IS
BEGIN
   DBMS_OUTPUT.PUT_LINE('ODCIAggregateIterate(' || self.analytics_window.COUNT || ')');

  -- Add record to collection
  self.analytics_window.extend();
  self.analytics_window(self.analytics_window.COUNT) :=  value;
  RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_ratio_to_report_col_impl, returnValue OUT matt_ratio_to_report_tab, flags IN NUMBER) RETURN NUMBER IS
BEGIN
   DBMS_OUTPUT.PUT_LINE('ODCIAggregateTerminate(' || self.analytics_window.COUNT || ' - flags: ' || flags || ')');
  IF flags = 1 THEN
    returnValue := self.analytics_window;
  END IF;
  RETURN ODCIConst.Success;
EXCEPTION
  WHEN others THEN 
    DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK || ' ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_ratio_to_report_col_impl, ctx2 IN matt_ratio_to_report_col_impl) RETURN NUMBER IS
BEGIN
--   DBMS_OUTPUT.PUT_LINE('ODCIAggregateMerge(' || self.window_sum || ' - ' || ctx2.window_sum || ')');
  -- TODO: Add all elements from ctx2 window to self window
  RETURN ODCIConst.Success;
END;

-- ODCIAggregateDelete
MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_ratio_to_report_col_impl, value matt_ratio_to_report_rec) RETURN NUMBER IS
  l_ctr NUMBER;
BEGIN
   DBMS_OUTPUT.PUT_LINE('ODCIAggregateDelete(' || self.analytics_window.COUNT || ' - ' || value.value || ')');
   l_ctr := self.analytics_window.FIRST;
   <<window_loop>>
   WHILE l_ctr IS NOT NULL LOOP
     IF ( self.analytics_window(l_ctr).value = value.value ) THEN
       self.analytics_window.DELETE(l_ctr);
       DBMS_OUTPUT.PUT_LINE('... deleted slot ' || l_ctr);
       EXIT window_loop;
     END IF;
     l_ctr := self.analytics_window.NEXT(l_ctr);
   END LOOP;
  RETURN ODCIConst.Success;

END;  

END;
/

-- This function is the analytic version of Oracle's COLLECT function
--DROP FUNCTION matt_ratio_to_report;
CREATE OR REPLACE FUNCTION matt_ratio_to_report_col ( input matt_ratio_to_report_rec) RETURN matt_ratio_to_report_tab
PARALLEL_ENABLE AGGREGATE USING matt_ratio_to_report_col_impl;
/


-- This the actual function we want
CREATE OR REPLACE FUNCTION matt_ratio_to_report ( p_row_value NUMBER, p_report_window matt_ratio_to_report_tab ) RETURN NUMBER IS
  l_report_window_sum NUMBER := 0;
  l_counter NUMBER := NULL;
BEGIN
  IF p_row_value IS NULL or p_report_window IS NULL THEN
    RETURN NULL;
  END IF;

  -- Compute window sum
  l_counter := p_report_window.FIRST;
  WHILE l_counter IS NOT NULL LOOP
    l_report_window_sum := l_report_window_sum + NVL(p_report_window(l_counter).value,0);
    l_counter := p_report_window.NEXT(l_counter);
  END LOOP;

  RETURN p_row_value / NULLIF(l_report_window_sum,0);
END matt_ratio_to_report;  



-- Create some test data
--DROP TABLE matt_test_data;
CREATE TABLE matt_test_data ( x, group# ) PARALLEL 4
AS SELECT rownum, ceil(rownum / 10) group# FROM DUAL CONNECT BY ROWNUM <= 50000;


-- TESTER 9/30
with test as (
SELECT d.x,
       CEIL (d.x / 10) group#,
       ratio_to_report (d.x) OVER (PARTITION BY d.group#) oracle_rr,
       matt_ratio_to_report (
         d.x,
         matt_ratio_to_report_col (matt_ratio_to_report_rec (d.x)) OVER (PARTITION BY d.group#)) custom_rr
FROM   matt_test_data d )
SELECT /*+ PARALLEL */ test.*, case when test.oracle_rr != test.custom_rr then 'Mismatch!' Else null END test_results from test 
--where oracle_rr != custom_rr
ORDER BY test_results nulls last, x; 

答案 1 :(得分:1)

我发现创建具有多个参数的自定义聚合的唯一方法是使用所需数量的元素创建新的TYPE,然后将该类型的实例传递给聚合:

首先定义结构以保存所需的所有“参数”:

create or replace type wrapper_type as object
(
   raw_grade integer,
   config_data varchar
);
/

然后创建聚合:

CREATE OR REPLACE TYPE analytic_wrapper AS OBJECT
(
  .. variables you might need

  STATIC FUNCTION ODCIAggregateInitialize(actx IN OUT wrapper_type) RETURN NUMBER,
  MEMBER FUNCTION ODCIAggregateIterate(self  IN OUT wrapper_type, val IN wrapper_type) RETURN NUMBER,
  MEMBER FUNCTION ODCIAggregateTerminate(self IN wrapper_type, returnValue OUT number, flags IN NUMBER) RETURN NUMBER,
  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT wrapper_type, ctx2 IN  wrapper_type) RETURN NUMBER
);
/

然后,您需要在type body中实现实际的聚合逻辑。完成后,您可以使用以下内容:

select analytic_wrapper(wrapper_type(G.raw_grade, P.course_config_data))
from ... 

以上内容或多或少是从内存中编写的,所以我很确定它充满了语法错误,但它应该让你开始。

手册中提供了更多详细信息和示例:http://docs.oracle.com/cd/E11882_01/appdev.112/e10765/aggr_functions.htm#ADDCI026

The manual表示这样的聚合可以用作分析函数:

  

当用户定义的聚合用作分析函数时,将为每行的相应窗口计算聚合

答案 2 :(得分:1)

我有同样的需要。我发布了一个似乎有效的方法(它与我迄今为止尝试过的所有案例的本机 - Oracle ratio_to_report函数一致)。

我担心的是,它依赖于“{1}}和ODCIIterate方法始终按相同顺序调用的”事实“。我没有任何理由相信这种情况总是如此。我可能会记录一个SR,因为如果没有Oracle的澄清,我认为我不能使用这个版本。

尽管如此,我发布的是代码,因为它确实代表了问题的答案。

警告#1 - 此代码将状态存储在PL / SQL包中。我讨厌这个,但我没有看到任何替代方案,因为ODCITerminate仅将SELF作为ODCITerminate传递,而不是IN。除了丑陋之外,这意味着您不能在同一查询中使用自定义分析函数的多个用法(因为它们的状态将混合在一起)。我确信可以围绕这个限制进行编码(例如,给每个ODCI上下文一个唯一的值并为每个唯一的上下文保持不同的状态)。

警告#2 - 我的测试用例使用PARALLEL查询。我可以从IN OUT看到 并行运行。但是,它似乎并没有实例化和合并多个上下文,我真的想测试它,因为如果有什么方法破坏了这种方法,那就是那个。

这是代码。

explain plan

答案 3 :(得分:0)

Data Cartridge Developer's Guide涵盖了这些主题。 This section讨论了用户定义的分析函数。