Logo

SQL Server RunningPRODUCT Function

Updated 2023-11-14 15:16:30.180000

Description

Use the scalar function RunningPRODUCT to calculate the product of column values in an ordered resultant table, without the need for a self-join. The product is calculated for each value from the first value to the last value in the ordered group or partition. If the column values are presented to the functions out of order, an error message will be generated.

Syntax

SELECT [westclintech].[wct].[RunningPRODUCT](
  <@Val, float,>
 ,<@RowNum, int,>
 ,<@Id, tinyint,>)

Arguments

@Val

the value passed into the function. @Val is an expression of type float or of a type that can be implicitly converted to float.

@RowNum

the number of the row within the group for which the product is being calculated. If @RowNum for the current row in a set is less than or equal to the previous @RowNum and @RowNum is not equal to 1, an error message will be generated. @RowNum is an expression of type int or of a type that can be implicitly converted to int.

@Id

a unique identifier for the RunningPRODUCT calculation. @Id allows you to specify multiple moving products within a resultant table. @Id is an expression of type tinyint or of a type that can be implicitly converted to tinyint.

Return Type

float

Remarks

If @Id is NULL then @Id = 0.

To calculate moving products, use the MovingPRODUCT function.

If @RowNum is equal to 1, RunningPRODUCT is equal to @Val

@RowNum must be in ascending order.

There may be cases where the order in which the data are returned to the function and the order in which the results are returned are different, generally due to parallelism. You can use OPTION(MAXDOP 1) or OPTION(MAXDOP 1,FORCE ORDER) to help eliminate this problem.

Examples

In this example we simply calculate the product of x after each row.

SELECT *,
       wct.RunningProduct(   x,   --@Val
                             rn,  --@RowNum
                             NULL --@Id
                         ) as PRODUCT
FROM
(
    VALUES
        (1, 1),
        (2, 0.5),
        (3, 0.333333333333333),
        (4, 0.25),
        (5, 0.2),
        (6, 0.166666666666667),
        (7, 0.142857142857143),
        (8, 0.125),
        (9, 0.111111111111111),
        (10, 0.1)
) n (rn, x);

This produces the following result.

rnxPRODUCT
11.0000000000000001
20.5000000000000000.5
30.3333333333333330.166666666666666
40.2500000000000000.0416666666666666
50.2000000000000000.00833333333333332
60.1666666666666670.00138888888888889
70.1428571428571430.000198412698412699
80.1250000000000002.48015873015873E-05
90.1111111111111112.75573192239859E-06
100.1000000000000002.75573192239859E-07

You can combine the RunningPRODUCT function with other arithmetic operations. In this example, we calculate the geometric return for a portfolio by adding 1 to the monthly return figures and then subtracting 1 from the result.

SELECT *,
       wct.RunningPRODUCT(1 + r, ROW_NUMBER() OVER (ORDER BY dt), NULL) - 1 as [
                 Geometric Return]
FROM
(
    VALUES
        ('2013-01-31', 0.099),
        ('2013-02-28', 0.006),
        ('2013-03-31', 0.086),
        ('2013-04-30', 0.064),
        ('2013-05-31', 0.003),
        ('2013-06-30', -0.011),
        ('2013-07-31', 0.046),
        ('2013-08-31', -0.012),
        ('2013-09-30', 0.069),
        ('2013-10-31', 0.094),
        ('2013-11-30', 0.073),
        ('2013-12-31', -0.021)
) n (dt, r);

This produces the following result.

dtrGeometric Return
2013-01-310.0990.099
2013-02-280.0060.105594
2013-03-310.0860.200675084
2013-04-300.0640.277518289376
2013-05-310.0030.281350844244128
2013-06-30-0.0110.267255984957443
2013-07-310.0460.325549760265485
2013-08-31-0.0120.309643163142299
2013-09-300.0690.400008541399118
2013-10-310.0940.531609344290635
2013-11-300.0730.643416826423851
2013-12-31-0.0210.60890507306895

In this example, we combine the XLeratorDB LAG function with the RunningPRODUCT function to calculate the geometric returns using the portfolio values.

SELECT *,
       wct.RunningProduct(
                             val / wct.LAG(val, 1, NULL, ROW_NUMBER() OVER (ORDER 
                                       BY dt), NULL),
                             ROW_NUMBER() OVER (ORDER BY dt),
                             NULL
                         ) - 1 as [Geometric Return]
FROM
(
    VALUES
        ('2012-12-31', 100000),
        ('2013-01-31', 106200),
        ('2013-02-28', 110448),
        ('2013-03-31', 114865.92),
        ('2013-04-30', 119115.96),
        ('2013-05-31', 120188),
        ('2013-06-30', 127399.28),
        ('2013-07-31', 127781.48),
        ('2013-08-31', 131231.58),
        ('2013-09-30', 136743.31),
        ('2013-10-31', 148639.98),
        ('2013-11-30', 146559.02),
        ('2013-12-31', 154619.77)
) n (dt, val);

This produces the following result.

dtvalGeometric Return
2012-12-31100000.00NULL
2013-01-31106200.000.0620000000000001
2013-02-28110448.000.10448
2013-03-31114865.920.1486592
2013-04-30119115.960.1911596
2013-05-31120188.000.20188
2013-06-30127399.280.2739928
2013-07-31127781.480.2778148
2013-08-31131231.580.3123158
2013-09-30136743.310.3674331
2013-10-31148639.980.4863998
2013-11-30146559.020.4655902
2013-12-31154619.770.5461977

In this example we use the LAG and the RunningPRODUCT functions to calculate portfolio returns and compare them to the returns on a benchmark. Note that the @Id parameter must be unique for each invocation of the LAG function and for each invocation of the RunningPRODUCT function but that the same @Id parameter can be used in each function once.

SELECT dt,
       port / wct.LAG(   port,                            --@val
                         1,                               --@Offset
                         NULL,                            --@DefaultValue
                         ROW_NUMBER() OVER (ORDER BY dt), --@RowNum
                         0                                --@id
                     ) - 1 as [Monthly Portfolio],
       wct.RunningProduct(   port / wct.LAG(port,                            
                 --@val
                                            1,                               
                                                      --@offset
                                            NULL,                            
                                                      --@DefaultValues
                                            ROW_NUMBER() OVER (ORDER BY dt), 
                                                      --@RowNum
                                            1                                
                                                      --@id
                                        ),                    --@val
                             ROW_NUMBER() OVER (ORDER BY dt), --@RowNum
                             0                                --@id
                         ) - 1 as [Y-T-D Portfolio],
       bmk / wct.LAG(   bmk,                             --@val
                        1,                               --@offset
                        NULL,                            --@DefaultValue
                        ROW_NUMBER() OVER (ORDER BY dt), --@RowNum
                        2                                --@Id
                    ) - 1 as [Monthly Benchmark],
       wct.RunningProduct(   bmk / wct.LAG(bmk,                             
                 --@Val
                                           1,                               
                                                     --@Offset
                                           NULL,                            
                                                     --@DefaultValue
                                           ROW_NUMBER() OVER (ORDER BY dt), 
                                                     --@RowNum
                                           3                                
                                                     --@Id
                                       ),                     --@Val
                             ROW_NUMBER() OVER (ORDER BY dt), --@RowNum
                             1
                         ) - 1 as [Y-T-D Benchmark]
FROM
(
    VALUES
        ('2012-12-31', 100000, 1426.19),
        ('2013-01-31', 106200, 1498.11),
        ('2013-02-28', 110448, 1514.68),
        ('2013-03-31', 114865.92, 1569.19),
        ('2013-04-30', 119115.96, 1597.57),
        ('2013-05-31', 120188, 1630.74),
        ('2013-06-30', 127399.28, 1606.28),
        ('2013-07-31', 127781.48, 1685.73),
        ('2013-08-31', 131231.58, 1632.97),
        ('2013-09-30', 136743.31, 1681.55),
        ('2013-10-31', 148639.98, 1756.54),
        ('2013-11-30', 146559.02, 1805.81),
        ('2013-12-31', 154619.77, 1810.65)
) n (dt, port, bmk);

This produces the following results.

dtMonthly PortfolioY-T-D PortfolioMonthly BenchmarkY-T-D Benchmark
2012-12-31NULLNULLNULLNULL
2013-01-310.06200000000000010.06200000000000010.05042806358199110.0504280635819911
2013-02-280.040.104480.01106060302648020.0620464314011457
2013-03-310.040.14865920.03598779940317430.100267145331267
2013-04-300.03700000835757030.19115960.0180857639928880.120166317250857
2013-05-310.008999969441542350.201880.02076278347740650.14342408795462
2013-06-300.06000000000000010.2739928-0.01499932545960740.126273497921034
2013-07-310.003000016954569860.27781480.04946211121348720.181981362932008
2013-08-310.02700000031303440.3123158-0.03129801332360460.14498769448671
2013-09-300.04200002773722610.36743310.02974947488318840.179050477145402
2013-10-310.08700001484533340.48639980.04459575986441090.231631129092197
2013-11-30-0.01400000188374640.46559020.02804946087194150.266177718256334
2013-12-310.055000026610440.54619770.002680237677274990.269571375482931