I figured out how to get a Spark UDF to return a fixed number of columns known ahead of time. But how can a Spark UDF return an arbitrary number of columns which might be different for each row?
I'm using Spark 3.3.0 with Java 17. Let's say I have a DataFrame containing 1,000,000 people. For each person I want to look up each year's salary (e.g. from a database), but each person might have salaries for different years available. If I knew just had years 2020 and 2021 available, I would do this:
StructType salarySchema = createStructType(List.of(createStructField("salary2020",
createDecimalType(12, 2), true), createStructField("salary2021",
createDecimalType(12, 2), true)));
UserDefinedFunction lookupSalariesForId = udf((String id) -> {
// TODO look up salaries
return RowFactory.create(salary2020, salary2021);
}, salarySchema).asNondeterministic();
df = df.withColumn("salaries", lookupSalariesForId.apply(col("id")))
.select("*", "salaries.*");
That is Spark's roundabout way of loading multiple values from a UDF into a single column and then splitting them out into separate columns.
So what if one person only has salaries from 2003 and 2004, while another person has salaries from 2007, 2008, and 2009? I would want to create columns salary2003 and salary2004 for the first person; and then salary2007, salary2008, salary2009 for the second person. How would I do that with a UDF? (I know how to dynamically create an array to pass back via RowFactory.create(). The problem is that the schema related to the UDF schema is defined outside the UDF logic.)
Or is there some better approach altogether with Spark? Should I be creating a separate lookup DataFrame altogether of just person IDs and a column for each possible salary year, and then join them somehow, like we would do in the relational database world? But what benefit would a separate DataFrame give me, and wouldn't I be back to square one to construct it? Of course I could construct it manually in Java, but I wouldn't gain the benefit of the Spark engine, parallel executors, etc.
In short, what is the best way in Spark to dynamically add an arbitrary number of columns for each row in an existing DataFrame, based upon each row's identifier?