I really like the ANY_VALUE aggregate function. But since it was added in Oracle 19c, I can’t use it in my Oracle XE database, as currently the latest version of XE is 18c.
So I decided to implement it as a user-defined function using the Oracle Data Cartridges Interface.
Note that this implementation is for VARCHAR2. If the function is used on other data types, the regular rules for implicit conversion apply.
SQL> create type any_value_string_t as object 2 ( 3 v_value varchar2(4000), 4 static function odciaggregateinitialize(sctx in out any_value_string_t) return number, 5 member function odciaggregateiterate 6 ( 7 self in out any_value_string_t, 8 value in varchar2 9 ) return number, 10 member function odciaggregatemerge 11 ( 12 self in out any_value_string_t, 13 ctx2 in any_value_string_t 14 ) return number, 15 member function odciaggregateterminate 16 ( 17 self in any_value_string_t, 18 returnvalue out varchar2, 19 flags in number 20 ) return number 21 ); 22 / Type created. SQL> create type body any_value_string_t as 2 3 static function odciaggregateinitialize(sctx in out any_value_string_t) return number is 4 begin 5 sctx := any_value_string_t(null); 6 return odciconst.success; 7 end; 8 9 member function odciaggregateiterate 10 ( 11 self in out any_value_string_t, 12 value in varchar2 13 ) return number is 14 begin 15 if self.v_value is null then 16 self.v_value := value; 17 end if; 18 return odciconst.success; 19 end; 20 21 member function odciaggregateterminate 22 ( 23 self in any_value_string_t, 24 returnvalue out varchar2, 25 flags in number 26 ) return number is 27 begin 28 returnvalue := self.v_value; 29 return odciconst.success; 30 end; 31 32 member function odciaggregatemerge 33 ( 34 self in out any_value_string_t, 35 ctx2 in any_value_string_t 36 ) return number is 37 begin 38 if self.v_value is null then 39 self.v_value := ctx2.v_value; 40 end if; 41 return odciconst.success; 42 end; 43 44 end; 45 / Type body created. SQL> begin 2 $IF DBMS_DB_VERSION.ver_le_18 $THEN 3 execute immediate q''create function any_value (p_value varchar2) return varchar2 4 parallel_enable 5 aggregate using any_value_string_t;''; 6 execute immediate q''grant execute on any_value to public''; 7 execute immediate q''create public synonym any_value for any_value''; 8 $ELSE 9 raise_application_error(-20000,'ANY_VALUE is now supported by Oracle'); 10 $END 11 end; 12 / PL/SQL procedure successfully completed.
Now any_value can be used just like any built-in aggregate function:
SQL> conn hr/hr Connected. SQL> select banner from v$version; BANNER ------------------------------------------------------------------------------------------------------------------------ Oracle Database 18c Express Edition Release 18.0.0.0.0 - Production 1 row selected. SQL> select d.department_id, 2 any_value(d.department_name) department_name, 3 count(*) number_of_employees 4 from employees e, 5 departments d 6 where d.department_id = e.department_id 7 group by d.department_id; DEPARTMENT_ID DEPARTMENT_NAME NUMBER_OF_EMPLOYEES ------------- -------------------- ------------------- 10 Administration 1 20 Marketing 2 30 Purchasing 6 40 Human Resources 1 50 Shipping 45 60 IT 5 70 Public Relations 1 80 Sales 34 90 Executive 3 100 Finance 6 110 Accounting 2 11 rows selected.