I don't want to use a table variable or comma-delimited list if I can avoid it.
TVP is the way to go. But if you don't want to define table type you could use old-fashioned XML:
CREATE TABLE tab(id INT, a INT, b INT, c INT, d INT);
INSERT INTO tab(id,a,b,c,d) VALUES(1,1,NULL,NULL,NULL);
INSERT INTO tab(id,a,b,c,d) VALUES(2,2,2,NULL,NULL);
INSERT INTO tab(id,a,b,c,d) VALUES(3,3,3,3,NULL);
INSERT INTO tab(id,a,b,c,d) VALUES(4,4,4,4,4);
Function:
CREATE FUNCTION dbo.my_func(@input XML)
RETURNS INT
AS
BEGIN
RETURN
(
SELECT SUM(a.b.value('.', 'INT')) --any logic here, for simplicity's sake SUM
FROM @input.nodes('/*') AS sub(c)
CROSS APPLY sub.c.nodes('@*') a(b)
);
END;
And call:
SELECT t1.*, sub.*, dbo.my_func(sub.x) AS result
FROM tab t1
CROSS APPLY (SELECT a,b,c,d
FROM (SELECT 1) t2(q) FOR XML AUTO) sub(x);
DBFiddle Demo
Output:
┌────┬───┬──────┬──────┬──────┬───────────────────────────────┬────────┐
│ id │ a │ b │ c │ d │ x │ result │
├────┼───┼──────┼──────┼──────┼───────────────────────────────┼────────┤
│ 1 │ 1 │ null │ null │ null │ <t2 a="1"/> │ 1 │
│ 2 │ 2 │ 2 │ null │ null │ <t2 a="2" b="2"/> │ 4 │
│ 3 │ 3 │ 3 │ 3 │ null │ <t2 a="3" b="3" c="3"/> │ 9 │
│ 4 │ 4 │ 4 │ 4 │ 4 │ <t2 a="4" b="4" c="4" d="4"/> │ 16 │
└────┴───┴──────┴──────┴──────┴───────────────────────────────┴────────┘
EDIT:
No, it is not possible to define UDF with variable args like COLAESCE. The best you can get is:
LOWEST2(a1, a2)
LOWEST3(a1, a2, a3)
LOWEST4(a1, a2, a3, a4)
...
LOWESTn(a1, a2, a3, a4, ..., an)
Please note that function names are different. Plus you need to use data type that will satisfy most of your cases or even sqlvariant.
sql_variantparameters, but it may be useful.