How to return several arrays of doubles as a table in C# SQLCLR function
-
08-03-2021 - |
Question
What I have is a CLR SqlFunction
that produces 3 arrays of doubles. I would like this function to return something appropriate so that the FillRowMethod
can output it for me as a table in T-SQL. It works fine for 1 array but I'm having trouble extending it to several. I'm mostly unsure on what to return from my method. Some code below:
[SqlFunction(DataAccess = DataAccessKind.Read, FillRowMethodName = "FillRow",
TableDefinition = "impliedVol float, maturity float, strike float")]
public static IEnumerable getStrippedCapletVolatilitiesFromCapVolatilityCurve(
string uploadDate, double strike, double yearsForward, double intervalDuration,
string curve, string surface)
//Create 3 arrays of doubles
double[] array1;
double[] array2;
double[] array3;
return [???];
}
public static void FillRow(object obj,
out SqlDouble impliedVol, out SqlDouble maturity, out SqlDouble strike)
{
//impliedVol = (double)obj; //This is what I do if only returning one array
}
EDIT:
Based on feedback this is my new attempted solution.
public static IEnumerable getStrippedCapletVolatilitiesFromCapVolatilityCurve(string uploadDate, double strike, double yearsForward, double intervalDuration, string curve, string surface)
{
//omitted code above this line.
CapletStipping thisCapletStripping = new CapletStipping(maturities, forwardRates, discountingRates, intervalDuration);
double[][] theseStrippedCapletVols = thisCapletStripping.getCapletCurveForGivenStrike(flatVols, strike);
List<capletVolatilityNode> capletVolatilitiesList = new List<capletVolatilityNode>(theseStrippedCapletVols[0].Length);
for (int i = 0; i < theseStrippedCapletVols[0].Length; i += 1)
{
capletVolatilityNode thisCapletVolatilityNode = new capletVolatilityNode(theseStrippedCapletVols[0][i], theseStrippedCapletVols[1][i], theseStrippedCapletVols[2][i]);
capletVolatilitiesList[i] = thisCapletVolatilityNode;
}
return capletVolatilitiesList; // theseStrippedCapletVols;
}
public class capletVolatilityNode
{
public double impliedVol;
public double maturity;
public double strike;
public capletVolatilityNode(double impliedVol_, double maturity_, double strike_)
{
impliedVol = impliedVol_;
maturity = maturity_;
strike = strike_;
}
}
public static void FillRow(Object obj, out SqlDouble impliedVol, out SqlDouble maturity, out SqlDouble strike)
{
capletVolatilityNode row = (capletVolatilityNode)obj;
impliedVol = Convert.ToDouble(row.impliedVol);
maturity = Convert.ToDouble(row.maturity);
strike = Convert.ToDouble(row.strike);
}
Solution
If you are wanting to return the 3 arrays as 3 separate result sets, that is not possible for a function, whether SQLCLR or T-SQL. You would need to create a stored procedure instead in order to return multiple result sets.
If the 3 arrays represent 3 columns of a single result set such that they will all have the same number of items and the index value of one is conceptually associated with the same index value of the others (i.e. array1[x]
is associated with array2[x]
and array3[x]
, while array1[y]
is associated with array2[y]
and array3[y]
, and so on), then a simple array is the wrong type of collection. You can only return a single collection where each item/element in the collection represents a row in the result set (or at least enough info to construct the desired row). That singular collection, when returned from the SqlFunction
method, is iterated over, calling the FillRowMethod
for each item/element. One item/element is passed into the FillRowMethod
which constructs the final result set structure and values and passes them back (hence you have the opportunity to create and/or translate values from the original item before passing them back).
In this latter case, you would need to create a class similar to:
private class volatility
{
public double impliedVol;
public double maturity;
public double strike;
}
Then, create a generic list of those in your getStrippedCapletVolatilitiesFromCapVolatilityCurve
method, add a new item to that collection for every row to return, and then return that list/collection. Your FillRowMethod
will be called with the first parameter (as object
) being of type volatility
. That is where you will populate the out
params from those properties of volatility
. For example:
private static void FillRow(object obj,
out SqlDouble impliedVol, out SqlDouble maturity, out SqlDouble strike)
{
volatility row = (volatility)obj;
impliedVol = new SqlDouble(row.impliedVol);
maturity = new SqlDouble(row.maturity);
strike = new SqlDouble(row.strike);
}
Now, it might be possible to handle this as a two-dimensional array (i.e. double[][]
) that you return from the main SqlFunction
, but then the FillRow
method would be sent a double[]
since the first dimension is broken out into individual calls to the FillRow
method. I've never tried this specific approach, but it should work as follows:
private static void FillRow(object obj,
out SqlDouble impliedVol, out SqlDouble maturity, out SqlDouble strike)
{
double[] row = (double[])obj;
impliedVol = new SqlDouble(row[0]);
maturity = new SqlDouble(row[1]);
strike = new SqlDouble(row[2]);
}
ALSO:
It just occurred to me that you could even forgo the generic list / collection and stream the contents of the double[][]
array out to the result set, one item / row at a time. Go ahead and try this:
public static IEnumerable getStrippedCapletVolatilitiesFromCapVolatilityCurve(...)
{
//omitted code above this line.
CapletStipping thisCapletStripping =
new CapletStipping(maturities, forwardRates, discountingRates, intervalDuration);
double[][] theseStrippedCapletVols =
thisCapletStripping.getCapletCurveForGivenStrike(flatVols, strike);
// THIS PART IS DIFFERENT -- begin
capletVolatilityNode thisCapletVolatilityNode = new capletVolatilityNode();
for (int i = 0; i < theseStrippedCapletVols[0].Length; i += 1)
{
thisCapletVolatilityNode.impliedVol = theseStrippedCapletVols[0][i];
thisCapletVolatilityNode.maturity = theseStrippedCapletVols[1][i];
thisCapletVolatilityNode.strike = theseStrippedCapletVols[2][i];
yield return thisCapletVolatilityNode; // return rows individually
}
return; // cannot return anything when using "yield return"
// THIS PART IS DIFFERENT -- end
}
private class capletVolatilityNode
{
public double impliedVol;
public double maturity;
public double strike;
}
There are some restrictions when using yield return
, but if your process allows for this construct, then this will not only be faster, but also take up less memory. These benefits are due to this code skipping the step of copying the results of the getCapletCurveForGivenStrike()
method to a separate collection (i.e the Generic List) just to return it to T-SQL (in which case you need to wait while it copies the collection and consumes more memory).
On a related note: use the Sql*
types for the input parameters instead of standard .NET types. Meaning, use SqlString
instead of string
and SqlDouble
instead of double
. You can then easily get the .NET native type from those via the Value
property that all of the Sql*
types have (e.g. SqlString.Value
passes back a string
, etc).
For more info on working with SQLCLR in general, please visit: SQLCLR Info