Get max value from nested objects

Hi, I need help to get the max value from a nested object property.
The objects are structured as follows.

class Person: Object {
    @objc dynamic var name: String?
    @objc dynamic var age: Int?
    @objc dynamic var city: String?
    let dogs = List<Pet>()
}

class Pet: Object {
    @objc dynamic var name: String?
    @objc dynamic var type: String?
    @objc dynamic var age: Int?
} 

I tried:

let city = "Los Angeles"
// first attempt
let personsFromLA = realm.objects(Person.self).filter("city == %@", city)
let maxAge = personsFromLA.sorted(byKeyPath: "dogs.age").last

// second attempt
let allPersons = realm.objects(Person.self)
let oldesDog = allPersons.filter("city == %@", city).filter("SUBQUERY(dogs, $dog, $dog.age.@max)")
let maxAge = oldesDog.age

Please help me to understand why this is not working. I did read the documentation but I guess I didn’t get it.
I do get the Object structure from extern and I can not change it for now. But I really do need to get the max value.

thanks in advance

Welcome to the foums!

I can tell you why it’s ‘not working’; this line

let personsFromLA = realm.objects(Person.self).filter("city == %@", city)

returns a results class full of person objects - anyone that lives in LA, as the variable name indicates

and then this line

let maxAge = personsFromLA.sorted(byKeyPath: "dogs.age").last

would also returns a person (the last person) from a group of persons and not an age.

We need more info:

You’re expected results are not clear; do you want to get the oldest dog in LA? Or do you want to get the maxAge of the oldest person in LA or… something else

It looks like you’re trying to get the oldest dog but wanted clarity on that.

While we’re waiting…

IF your use case is to get the the person with the oldest dog, here’s a solution. This is not very scaleable as we’re processing everything in memory which, with a LOT of objects, could overwhelm the device. It’s also very loose code and could be shortened considerably - I left it verbose for readability.

let people = realm.objects(Person.self) //get da people

// a struct to hold a person and their oldest dogs age
struct ByAge {
    var person: Person!
    var dogAge = 0
}

//and array to store all of the above objects in, to be sorted by age after the array is populated
var ageArray = [ByAge]()

//iterate over all the people, creating a ByAge object for each to maintain a link to the person object
//  and store the eldest dogs age
people.forEach { person in
    if person.dogList.count > 0 {
        let maxDog = person.dogList.sorted(byKeyPath: "age").last! //old dog
        let personWithMaxDogAge = ByAge(person: person, dogAge: maxDog.age)
        ageArray.append(personWithMaxDogAge)
    }
}

//RELEASE THE HOUNDS!... oh, uh, sort the array
let sortedArray = ageArray.sorted(by: { $0.dogAge < $1.dogAge })

//get the last one sorted, which will be the person with the eldest dog
let personWithOldestDog = sortedArray.last!

//output the persons name and the dogs age
print(personWithOldestDog.person.name, personWithOldestDog.dogAge)

…and of course, a non-in memory solution is optimal

//get the oldest dog
let lastDog = realm.objects(Dog.self).sorted(byKeyPath: "age").last!

//get the person that owns the oldest dog
let personWhoOwnsTheDog = realm.objects(Person.self).where { $0.dogList.contains(lastDog)}.last!

//output to console
print(personWhoOwnsTheDog.name, lastDog.age)

Ideally you should update the Dog class to contain an inverse relationship back to the owner which then simplifies things even further.

Hallo Jay, i really appreciate your effort. Sorry you have to wait for the response so long.

Actually, I only need ONE age. The age of the oldest pet in LA

// assuming this gives me the Person from LA with the oldest pet
let person = personsFromLA.sorted(byKeyPath: "pets.age").last
// the person still could have a bunch of pets ... get the oldest one by loop over the list
let age = person.pets.map { $0.age }.sorted().last ?? 0

your second solution does not fit my needs as i am really do have a LOT of Data.

The third attempt could work if i could manage to get the person from LA in the where clause but in the meantime a lot did change.

The .net guy who build the db for me is back and tried to get the inverse relationship working. The Db looks good in the realm browser after he build it up. After importing the db into my swift project, the list of pets is empty. Of course i did update the schema. I don’t know what goes wrong. Now we tried to embedd the data. I guess there is no reason to have them in a separate table. I still not sure if or how much this will help us as I am right now on another but similar problem …

I would like to thank you once again for your detailed answer.

(ps i just noticed that i mixed up pets and dogs but i guess you get the point)

It’s not clear why someone else is “building the db” for you or why it’s being imported into your project. A Realm Database is a standalone file and would not normally need to be imported; it’s just opened via a config that points to that file.

Also, how the database was ‘built’ will directly relate to how you use it. e.g. if it was not built with an inverse relationship of dogs back to persons, you can’t really add it later without re-doing/updating those objects.

Realm has no tables - it’s an object graph database - having people and dogs in different realm files could certainly be part of the issue if that’s the case.

Perhaps clarifying the issue would help us to help you resolve it. I think my non-in memory solution would really be the way to go - we use it in a current project.

1 Like

Thanks again for your answer.

First of, the app is using data from a way bigger project which is using mysql and postgresql (i belief). In my app, i didn’t need all the data and i only need to read it. Under these circumstances, building a realm file this way is pretty common i would say.

I did use realm relations before, even the inverse relationship … with no problem. But as i said, if the realm file was build with .net and then read from swift the schema gets changed. It looks like there is some kind of silend migration going on. We checked the documentation back and forth. The .net part seems to be right but the swift part too. Even if i open the .net realm file in the realm browser and let me generate the schema … after running the app the realm file looks different.

As i am a little under pressure at the moment, i will have a look at this point later one … fake it until you make it. As a Workarount the Person does have a petsMaxAge Property which i use if there is no other filtering is going on. If there are other filter (dependencies) i do loop over the result set. In the most cases the other filters do shrink the results to a point where this no issue.

just to clarify:
When i say “imported” i just meant i added it to my swift project. Of course i just open the realm file via a config.

I am only have one realm file with to objects classes (Persons and Pets). which is now only one (persons with embedded pets) I know that realm has no tables and its graph based … I am really sorry i did use the wrong terminologie.
I guess it is also clear that we do not talk about Persons and Pets here :wink: have a nice weekend