Search code examples
iosswiftdatabaserealm

How Query and Count entries in a Realm Database


I'd like to build a search on which the user can filter down the results step-by-step. So with no choice set, there is a button which says e.g. "1,234,567 Results" and if you choose a color for example the results set shrinks... we all know this kind of search. I did build it many times, but this is the first time in Realm (and swift).

Lets Say I have 5 Persons in my Person Table, then there are about 145,224 Dog entries per Person and about 2,507,327 Cat entries per Dog. How do I query and Count nested Objects in Realm?

class Person: Object {
    @objc dynamic var name = ""
    let dogs = List<Dog>()
    // ...other Properties
}

extension Person {
    static func all(in realm: Realm = try! Realm()) -> Results<Person> {
        return realm.objects(Person.self)
    }
}

// counts -> 145,224 db entries per person
class Dog: Object {
    @objc dynamic var name = ""
    dynamic var Person: Person?
    let cats = List<Cats>()
    // ...other Properties as well
}

extension Dog {
    static func all(in realm: Realm = try! Realm()) -> Results<Dog> {
        return realm.objects(Dog.self)
    }
}

// counts -> 2,507,327 db entries per dogs
class Cat: Object {
    @objc dynamic var name = ""
    dynamic var Cat: Cat?
}

extension Cat {
    static func all(in realm: Realm = try! Realm()) -> Results<Cat> {
        return realm.objects(Cat.self)
    }
}


// Get the default Realm
let realm = try! Realm()

// Query Realm for all dogs
let dogs = Person.all(in: realm).flatMap { $0.dogs }
dogs.count // => takes ~20 seconds to count    

In other words, what is the fastest way to get (count) all Dog entries of all Persons (let the cats by side for now).

I tried to workaround the problem by limit the results to 1000. If the results are >1000, then label the button like so "> 1000 Results". But even than it takes very long (I guess the get all count anyway).

So what did I do wrong?


Solution

  • They way you were computing the count required all Dog objects to be loaded into memory which is really inefficient. This is why you were seeing such poor performance. You want to take advantage of Realm's lazy loading features. You may want to read up on that.

    I would update your Dog object by getting rid of your managed Person property and replace it with LinkingObjects. If you store a Dog in a Person.dogs List, then realm will create a back link to the Person for you. You will likely want to do the same thing with Cat. That way you can set up some really powerful nested queries.

    For convenience you can add a computed Person property to index into the LinkingObjects. Just know that you won't be able to use that property in any of your queries.

    class Dog: Object {
        @objc dynamic var name = ""
        let persons = LinkingObjects(fromType: Person.self, property: "dogs")
        var person: Person? { persons.first }
    }
    

    One way to compute the count is to query of all Person objects and sum the count of dogs for each Person.

    let count = realm.objects(Person.self).reduce(0) { result, person in
        return result + person.dogs.count
    }
    

    Another options is to query for Dog objects that have a corresponding Person. If a Dog is not in a Person.dogs List, then it won't show up the query.

    let realm = try! Realm()
    let count = realm.objects(Dog.self).filter("persons.@count > 0").count
    

    Either option is going to be much, much more efficient than what you had.

    Hope this helps.