Well, ultimately breaking it into blocks gives me complete control over the transaction log size. In this specific case, disk space is more important than performance. I can trade performance for disk space by reducing the block size. Also, the fixed block size means that the transaction log will not increase in size as a result of more source data. This solution seems to be acceptable.
I ended up with this:
DECLARE @i INT, @max-2 INT, @blocksize INT
SET @i = 0
SET @blocksize = 50000
SELECT @max-2 = MAX(id) FROM temp.OrderPrep
WHILE @i <= @max-2
BEGIN
IF @debug = 1 SELECT @i AS , @max-2 AS [MAX]
INSERT INTO fact.CustomerOrder
SELECT
-- SELECT List removed for brevity and privacy
FROM
temp.OrderPrep OP
LEFT JOIN dim.Customer C on C.CustomerID = OP.CustomerID
OUTER APPLY Lookup.dbo.fn_SalesRegion(
CASE
WHEN C.CustomerType = 'OEM-N' then 'NAT'
WHEN C.Channel = 'EXPORT' then 'EXP'
ELSE ShipToState
END, ShipToCountry) SR
OUTER APPLY Lookup.dbo.fn_IRRegion(ShipToState, ShipToZipcode, ShipToCountry) IRR
WHERE OP.[id] <= @i AND (OP.[id] > @i - @blocksize)
IF @debug = 1 PRINT 'Checkpoint ' + CAST(@i AS VARCHAR(50))
CHECKPOINT
SET @i = @i + @blocksize
END
Which really isn't much more code than the original script. It's not the most elegant solution in the world, but it works. Frankly, I've already spent too much time on this. File bloat, however, can become serious, so it was imperative that I spend however much time was needed to solve it the problem.
Besides SSIS, I can't think of any other ways of moving the data. I've avoided SSIS for a couple of reasons. The first is because I am unsure about the OUTER APPLY capabilities via the UDFs. The other is because this code works, and I want to minimize the possibility of introducing new bugs. That second reason applies to any other method of moving data.