Maybe it is easier to write a query using a reasonable limit of columns.
However, in this particular case, the dynamic query probably does not look complicated:
declare @c as int
select @c = max(c)
from (
select PersonId, count(distinct FeeId) as c
from PersonFee
group by PersonId
) as t
declare @sql varchar(max) = 'select PersonId, '
declare @i int = 1
while @i <= @c
begin
if @i <> 1 set @sql += ','
declare @j varchar(max) = @i
set @sql += '
max(case r when ' + @j + ' then FeeId end) as FeeId' + @j + ',
max(case r when ' + @j + ' then Start end) as Start' + @j + ',
max(case r when ' + @j + ' then [End] end) as End' + @j + ',
max(case r when ' + @j + ' then Amount end) as Amount' + @j
set @i += 1
end
set @sql += '
from (
select *, row_number() over (partition by PersonId order by Start) as r
from PersonFee
) t
group by PersonId
order by PersonId
'
exec (@sql)
By the way, the sample data contains duplicate FeeId for Person 83, If this is not accidental, then it is not clear how to pivot such data.