Use %ConstructClone (from %RegisteredObject)

Method %ConstructClone(deep As %Integer = 0, ByRef cloned As %String, location As %String) as %RegisteredObject

Clone the current object to a new object. If deep is 1 then this does a deep copy which will also copy any subobjects and if deep is 0 then it will create another reference to any subobjects and increment the reference count appropriately. It returns the new cloned object.

Note that even if deep=0 when you clone a parent object in a parent child relationship or a one object of a one to many relationship then it will construct clones of all the child/many objects. This is because a child/many object can only point at a single parent and so if we did not create a clone of these then you would have a relationship with zero items in it. If you really just want to clone the object without these child/many objects then pass deep=-1 to this method.

After the clone is constructed it will call %OnConstructClone(object,deep,.cloned) on the clone if it is defined so that you can perform any additional steps e.g. taking out a lock. This works just the same way as %OnNew() does.

The object is the oref of the original object that was cloned. The cloned array is just used internally when doing a deep clone to prevent recursive loops, do not pass anything in at all for this parameter on the initial call. If you write a %OnConstructClone and from here you wish to call %ConstructClone on another object pass in the cloned array, e.g. 'Do oref.%ConstructClone(1,.cloned)' so that it can prevent recursive loops.

The location is used internally to pass the new location for stream objects.

The answer has been given in French here by @Lorenzo Scalese 

You can create a custom class query like this: 

Class dc.Frank
{

Query QueryAFO() As %Query(ROWSPEC = "key1:%String,key2:%String,key3:%String,key4:%String,key5:%String,key6:%String,datavalue:%String") [ SqlProc ]
{
}

ClassMethod QueryAFOExecute(ByRef qHandle As %Binary) As %Status
{
    Set qHandle("node") = $Name(^AFO)
    Quit $$$OK
}

ClassMethod QueryAFOFetch(
	ByRef qHandle As %Binary,
	ByRef Row As %List,
	ByRef AtEnd As %Boolean) As %Status [ PlaceAfter = QueryAFOExecute ]
{
    Set sc = $$$OK
    Set qHandle("node") = $Query(@qHandle("node"), 1, data)

    If qHandle("node") = "" Set Row = "", AtEnd = $$$YES Quit $$$OK
    ; feeds the key x fields based on the subscripts of the global
    For i=1:1:$QLength(qHandle("node")) Set $List(Row, i) = $QSubscript(qHandle("node"), i)

    If i < 6 {  ; if we do not have 6 subscripts, we feed the rest with an empty string
        For j = i+1:1:6 Set $List(Row, j) = ""
    }

    Set $List(Row, 7) = data, AtEnd = $$$NO
    Quit sc
}

ClassMethod QueryAFOClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = QueryAFOExecute ]
{
	Kill qHandle Quit $$$OK
}

/// just for some test data
ClassMethod set()
{
    s ^AFO("Site","Ville")="66722,3743"
    s ^AFO("Site","Ville","111BB","OBT")=",MMM,XXX,"
    s ^AFO("Site","Ville","111OW","OBT")=",XXX,MMM,"
    s ^AFO("Site","Ville","AANVRBIBS","zzz")    =    "1^^1"
    s ^AFO("Site","Ville","AANVRBIBS","zzz","*","dut")    =    "*afhalen waar gevonden"
    s ^AFO("Site","Ville","AANVRBIBS","zzz","*","eng")    =    "*Pickup where found"
    s ^AFO("Site","Ville","AANVRBIBS","zzz","*","fre")    =    "*Lieu où trouvé"
}

}

You can then easily exploit it with the following query:

select *
from dc.Frank_QueryAFO()

In terminal mode, you can also simply use this line to display the results:

Do ##class(dc.Frank).QueryAFOFunc().%Display()

For my part, I carried out the test on IRIS 2023.2, there should be no incompatibility, but if you notice a problem do not hesitate to respond with the error message.