The Data-Relation queries in a parent-child hierarchy are synchronized automatically only when there is a browse object attached to the query. This attachment is done by assigning the relation's QUERY attribute to the browse's QUERY attribute. If you want to synchronize a hierarchy of queries under other circumstances, you use the SYNCHRONIZE method on any parent buffer handle:
buffer-handle:SYNCHRONIZE().
This causes the ProDataSet to traverse the ProDataSet hierarchy starting at buffer buffer-handle. It reopens each relation query for the current parent at each level, just as it happens automatically when you select a record in a browse or perform some ABL action that changes the record position in a parent buffer whose children are attached to browses. The synchronize behavior is not provided automatically in all cases because there are simply too many different ways in which the position could be changed and too many different responses that a developer might want. Always reopening all related queries is not appropriate, because of the expense involved.
You can decide when to synchronize by reacting to an event such as ON VALUE-CHANGED, or simply in conjunction with a language statement or method such as GET-NEXT, and explicitly doing the synchronize when necessary. Note that this synchronization affects only the implicit dynamic queries associated with Data-Relations when you are navigating a ProDataSet that has already been filled. It has nothing to do with the FILL operation itself, and using these queries is entirely optional. In many cases (perhaps even in most cases), your own ABL logic will instead use conventional nested FOR EACH blocks or queries to navigate through the levels of a ProDataSet, without using or caring about these implicit queries at all. This is part of why the overhead of opening them does not happen unless the relation queries have explicitly been attached to a browse.
As part of the SYNCHRONIZE() method, the AVM automatically positions to the first row in each relation query, in addition to reopening it on children of the current parent row. This is not done if the query is being browsed (with an ABL client browse widget) because the browse effectively forces a GET-FIRST already.
Doing this automatically spares the developer from having to write a GET-FIRST method on each child query. A typical block of code to navigate through the children of the current parent looks like this:
hChildQuery:GET-FIRST().
DO WHILE NOT hChildQuery:QUERY-OFF-END:
hChildQuery:GET-BUFFER-HANDLE(1):SYNCRHONIZE().
/* If there are further children */
/* Processing code for the current row goes here. */
hChildQuery:GET-NEXT().
END.
If you forget the GET-FIRST, the loop exits immediately because the query is "OFF-END" if there is no row at all in its buffer.
In addition, this is the only way to propagate a SYNCHRONIZE() through multiple parent-child levels. Consider the example of a three-level ProDataSet with tables ttCustomer, ttOrder, and ttOrderLine. The application does a SYNCHRONIZE() on the ttCustomer table when the user selects a different row in that table. Without doing an implicit GET-FIRST on the ttOrder table to position it to the first ttOrder for the newly selected ttCustomer, the relation query for the ttOrderLine table will not be properly reopened and filtered for OrderLines of the Customer's first Order, because there would be no current row in the ttOrder table.
Independent of all this, you can freely define queries of your own to navigate the tables in the ProDataSet after it has been filled, or even as part of custom code to populate one or more tables of a ProDataSet independent of its FILL method.