If the maximum number of Detail records is fixed and known then this can be done. The larger the number the more tedious the query is to code. That's why Nature gave us cut'n'paste.
The following query uses a couple of tricks. The Common Table Expression (aka Sub-Query Factoring) clause encapsulates the query on RegistrationDetail so we can easily refer to it in multiple places. The sub-query uses an Analytic function ROW_NUMBER() which allows us to identify each Detail record within the RegistrationID group. Both these features wwre introduced in Oracle 9i so they aren't new, but lots of people still don't know about them.
The main query uses Outer Joins to connect the Registration table multiple times to rows in the sub-query. It joins on RegistrationID and the derived DetNo.
SQL> with dets as
2 ( select
3 registrationid
4 , owner
5 , type
6 , distance
7 , detailid
8 , row_number() over (partition by registrationid
9 order by detailid) as detno
10 from registrationdetail )
11 select
12 reg.registrationid
13 , reg.somedate
14 , reg.totlength
15 , det1.detailid as detId1
16 , det1.owner as owner1
17 , det1.type as type1
18 , det1.distance as distance1
19 , det2.detailid as detId2
20 , det2.owner as owner2
21 , det2.type as type2
22 , det2.distance as distance2
23 , det3.detailid as detId3
24 , det3.owner as owner3
25 , det3.type as type3
26 , det3.distance as distance3
27 from registration reg
28 left join dets det1 on ( reg.registrationid = det1.registrationid
29 and det1.detno = 1 )
30 left join dets det2 on ( reg.registrationid = det2.registrationid
31 and det2.detno = 2 )
32 left join dets det3 on ( reg.registrationid = det3.registrationid
33 and det3.detno = 3 )
34 order by reg.registrationid
35 /
REGISTRATIONID SOMEDATE TOTLENGTH DETID1 OW TY DISTANCE1 DETID2 OW TY DISTANCE2 DETID3 OW TY DISTANCE3
-------------- --------- ---------- ---------- -- -- ---------- ---------- -- -- ---------- ---------- -- -- ----------
1 01-JAN-10 5 1 TD UB 1.5 2 AB US 2 3 TD UQ 4
2 01-FEB-10 15 4 AB UQ 13 5 AB UR 13.1
3 05-FEB-09 10 6 TD US 5
SQL>
Obviously if you have four Detail records per RegistrationID you will need four of those Outer Joins (and four sets of columns in the projection).
edit
I have just re-read your question and spotted the dread words "No maximum number". Sorry, in that case you're out of luck. The only way of solving this problem with a variable number of sets is with dynamic SQL, which you have effectively ruled out (because you would need to create additional schema objects).
edit 2
There is another solution, which is just about extracting the data and forgetting the layout. Oracle allows us to declare inline cursors, that is nested select
statements, in the projection alongside scalars. This passes the problem of displaying the output to a client tool.
In this version I use Oracle's built-in XML functionality to produce the output (on the basis that lots of tools can render XML these days). The RegistrationDetails records are group within an XMLElement called REG_DETAILS which is nested within each Registration record.
with dets as
( select
registrationid
, owner
, type
, distance
, detailid
, row_number() over (partition by registrationid
order by detailid) as detno
from registrationdetail )
select
xmlelement("AllRegistrations"
, xmlagg(
xmlelement("Registration"
, xmlforest( reg.registrationid
, reg.somedate
, reg.totlength
, ( select xmlagg(
xmlelement("RegDetail"
, xmlforest(dets.detailid
, dets.owner
, dets.type
, dets.distance
, dets.detno
)
)
)
from dets
where reg.registrationid = dets.registrationid
) as "RegDetails"
)
)
)
)
from registration reg
order by reg.registrationid
/