SQL-Aggregatsfunktion "PRODUCT" fehlt ...? Kein Problem - wir bauen sie selbst!
Auf einer Veranstaltung in München, während es um das Thema Analytische Funktionen ging, fragte
mich jemand nach einer Aggregatsfunktion für die Bildung eines Produktes - also nicht das Aufsummieren
der Werte wie bei SUM, sondern das multiplizieren - und in der Tat: eine solche Funktion gibt es in
Oracle nicht.
Nun kann man das mit PL/SQL natürlich auf einfachste Weise nachprogrammieren - aber eine solche
Funktion lässt sich nicht in SQL-Abfragen mit GROUP BY nutzen. Berechnungen für Datengruppen
in einer Tabelle müssen dann mühsam mit PL/SQL nachgebildet werden.
Aber das muss nicht sein. Es ist kaum bekannt, aber man kann eigene Aggregatsfunktionen in der
Datenbank hinterlegen. Das habe ich in einem sehr frühen Blog-Posting schonmal genutzt: Mit
der dort beschriebenen LIST-Funktion kann man VARCHAR2-Spalten auch von vor Oracle11g
zusammenfassen (in Oracle11g gibt es mit LISTAGG ja eine eingebaute Funktion). Um ein
User Defined Aggregate zu bauen, muss man eine Schnittstelle ausprogrammieren. Und hier ist
der Code für AGG_PRODUCT.
drop function agg_product;
drop type agg_product_t;
create type agg_product_t as object(
v_agg_product number,
static function ODCIAggregateInitialize(
sctx IN OUT agg_product_t
) return number,
member function ODCIAggregateIterate(
self IN OUT agg_product_t, value IN number
) return number,
member function ODCIAggregateTerminate(
self IN agg_product_t, returnValue OUT number, flags IN number
) return number,
member function ODCIAggregateMerge(
self IN OUT agg_product_t, ctx2 IN agg_product_t
) return number
);
/
sho err
create or replace type body agg_product_t is
static function ODCIAggregateInitialize(sctx IN OUT agg_product_t)
return number is
begin
sctx := agg_product_t(1);
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(
self IN OUT agg_product_t,
value IN number
) return number is
begin
self.v_agg_product:=self.v_agg_product * nvl(value, 1);
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(
self IN agg_product_t,
returnValue OUT number,
flags IN number
) return number is
begin
returnValue := self.v_agg_product;
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self IN OUT agg_product_t, ctx2 IN agg_product_t) return number is
begin
if ctx2.v_agg_product is null and self.v_agg_product is not null then
null;
elsif ctx2.v_agg_product is not null and self.v_agg_product is not null then
self.v_agg_product := self.v_agg_product * ctx2.v_agg_product;
elsif ctx2.v_agg_product is not null and self.v_agg_product is null then
self.v_agg_product := ctx2.v_agg_product * 1;
elsif ctx2.v_agg_product is null and self.v_agg_product is null then
null;
end if;
return ODCIConst.Success;
end;
end;
/
sho err
CREATE or replace FUNCTION agg_product (input number) RETURN number
PARALLEL_ENABLE AGGREGATE USING agg_product_t;
/
sho err
Die Funktion könnt Ihr danach sofort verwenden.
SQL> select deptno, agg_product(sal) product_sal from emp
2 group by deptno
DEPTNO PRODUCT_SAL
---------- ------------------------------
10 15925000000
20 23562000000000000
30 10153125000000000000
Auch als analytische Funktion kann sie verwendet werden.
select ename, agg_product(sal) over (order by sal desc rows between unbounded preceding and current row) product_sal from emp; ENAME PRODUCT_SAL ---------- --------------- KING 5000 FORD 15000000 SCOTT 45000000000 JONES 133875000000000 BLAKE 3,815437500E+17 CLARK 9,347821875E+20 : :
Das geht mit einer normalen PL/SQL-Funktion nun wirklich nicht; die ganze analytische "Power"
von SQL kann so ausgenutzt werden. Nicht jeder braucht wirklich eine AGG_PRODUCT-Funktion; aber
wenn Ihr Aggregate mit eigenen Funktionen bilden müsst, sind die User Defined Aggregates
auf jeden Fall einen Blick wert.
Kommentare:
Hallo Carsten,
die Verwendung von nvl bei ODCIAggregateIterate scheint mir überflüssig, da die Methode nur für nicht-NULLs aufgerufen wird:
"This routine is invoked for every non-NULL value in the underlying group."
Sollte man dann nicht auch auf die if-Abfrage bei ODCIAggregateMerge verzichten können?
Viele Grüße
Thomas
Hallo Carsten,
die Verwendung von nvl bei ODCIAggregateIterate sollte überflüssig sein, da ODCIAggregateIterate, laut Dokumentation, nur für nicht NULLs aufgerufen wird:
"This routine is invoked for every non-NULL value in the underlying group. NULL values are ignored during aggregation and are not passed to the routine."
Somit sollte auch die if-Abfrage bei ODCIAggregateMerge entfallen.
Gruß
Thomas
Hallo Thomas,
genaues Lesen der Dokumentation hilft in der Tat - dem ist nichts hinzuzufügen ...
Beste Grüße
-Carsten
Kommentar veröffentlichen