Update -- the materialized view thing won't work, because the query I was able to find to do all the string-splitting needed to populate the materialized view:
still takes half an hour (although that's an order of magnitude improvement over the OP, it's still far too long to refresh a materialized view), and
confuses Oracle so badly that it has no idea how to fast refresh the materialized view based on the query, even if it's a R/O materialized view. (The query cross joins to a subquery, which is what trips up Oracle, apparently -- having a complicated mess of subquery in your FROM clause just causes Oracle to complain and say "nope, can't fast refresh this, there's this big blob of subquery in your defining query I can't get rid of".)
So, I'm going to have to write out the side table myself, instead of letting Oracle maintain it for me. :sigh: