If you are using ORACLE then you can use these methods:
SQL Fiddle
There is no PRODUCT
aggregate function but you can write a custom aggregation function...
Oracle 11g R2 Schema Setup:
CREATE TYPE ProductAggregationType as OBJECT
(
product NUMBER,
STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT ProductAggregationType)
RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(self IN OUT ProductAggregationType,
value IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(self IN ProductAggregationType,
RETURNValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(self IN OUT ProductAggregationType,
ctx2 IN ProductAggregationType) RETURN NUMBER
);
/
CREATE OR REPLACE type body ProductAggregationType is
STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT ProductAggregationType)
RETURN NUMBER IS
BEGIN
sctx := ProductAggregationType(1);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate(self IN OUT ProductAggregationType, value IN NUMBER) RETURN NUMBER is
BEGIN
self.product := self.product * value;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate(self IN ProductAggregationType,
returnValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER is
BEGIN
returnValue := self.product;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge(self IN OUT ProductAggregationType, ctx2 IN ProductAggregationType) RETURN NUMBER is
BEGIN
self.product := self.product * ctx2.product;
RETURN ODCIConst.Success;
END;
END;
/
CREATE FUNCTION PRODUCT (input NUMBER) RETURN NUMBER
PARALLEL_ENABLE AGGREGATE USING ProductAggregationType;
/
CREATE TABLE data ( names, dates, "return" ) AS
SELECT 'A', TO_DATE( '31-01-2021', 'DD-MM-YYYY' ), 1.05 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '28-02-2021', 'DD-MM-YYYY' ), 0.97 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-03-2021', 'DD-MM-YYYY' ), 1.01 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '30-04-2021', 'DD-MM-YYYY' ), 1.04 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-05-2021', 'DD-MM-YYYY' ), 1.05 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '30-06-2021', 'DD-MM-YYYY' ), 0.97 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-07-2021', 'DD-MM-YYYY' ), 1.01 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-08-2021', 'DD-MM-YYYY' ), 1.02 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '30-09-2021', 'DD-MM-YYYY' ), 1.03 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-10-2021', 'DD-MM-YYYY' ), 0.90 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '30-11-2021', 'DD-MM-YYYY' ), 1.00 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-12-2021', 'DD-MM-YYYY' ), 1.12 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-01-2022', 'DD-MM-YYYY' ), 0.90 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '28-02-2022', 'DD-MM-YYYY' ), 1.10 FROM DUAL
UNION ALL SELECT 'A', TO_DATE( '31-03-2022', 'DD-MM-YYYY' ), 1.00 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-01-2021', 'DD-MM-YYYY' ), 1.02 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '28-02-2021', 'DD-MM-YYYY' ), 0.98 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-03-2021', 'DD-MM-YYYY' ), 1.03 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '30-04-2021', 'DD-MM-YYYY' ), 1.01 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-05-2021', 'DD-MM-YYYY' ), 1.02 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '30-06-2021', 'DD-MM-YYYY' ), 0.96 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-07-2021', 'DD-MM-YYYY' ), 1.05 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-08-2021', 'DD-MM-YYYY' ), 1.01 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '30-09-2021', 'DD-MM-YYYY' ), 1.02 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-10-2021', 'DD-MM-YYYY' ), 0.97 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '30-11-2021', 'DD-MM-YYYY' ), 1.02 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-12-2021', 'DD-MM-YYYY' ), 1.08 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-01-2022', 'DD-MM-YYYY' ), 0.95 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '28-02-2022', 'DD-MM-YYYY' ), 1.05 FROM DUAL
UNION ALL SELECT 'B', TO_DATE( '31-03-2022', 'DD-MM-YYYY' ), 1.00 FROM DUAL
/
Query 1:
SELECT names,
EXTRACT( YEAR FROM dates ) AS year,
PRODUCT( "return" ) AS total_return
FROM data
GROUP BY names, EXTRACT( YEAR FROM Dates )
ORDER BY year
Results:
| NAMES | YEAR | TOTAL_RETURN |
|-------|------|----------------|
| A | 2021 | 1.165456536894 |
| B | 2021 | 1.176962855452 |
| A | 2022 | 0.99 |
| B | 2022 | 0.9975 |
Query 2:
Or you can use the power of logarithms to calculate the product:
SELECT names,
EXTRACT( YEAR FROM dates ) AS year,
EXP(SUM(LN( "return" ))) AS total_return
FROM data
GROUP BY names, EXTRACT( YEAR FROM Dates )
ORDER BY year
Results:
| NAMES | YEAR | TOTAL_RETURN |
|-------|------|----------------|
| A | 2021 | 1.165456536894 |
| B | 2021 | 1.176962855452 |
| A | 2022 | 0.99 |
| B | 2022 | 0.9975 |