Phlippie Bosman
Phlippie Bosman

Reputation: 5710

Find Realm objects where any sub-objects satisfy all criteria

Background / context:

In RealmSwift, if I have an object that has a List of sub-objects, I can find all objects where any sub-objects satisfy some criteria by using ANY in my predicate.

For example, say I have the following schema:

import RealmSwift

class Parent: Object {
  @objc dynamic var name = "" // Used to identify query results
  var children = List<Child>()
}

class Child: Object {
  @objc dynamic var x = 0
  @objc dynamic var y = 0
}

And say I create and persist a parent like this:

let parent = Parent()

let child = Child()
child.x = 1
parent.children.append(child)

let realm = try! Realm()
try! realm.write { realm.add(parent) }

If I want to find all parents with children where x is 1, I can simply do a query like this:

let result = realm
  .objects(Parent.self)
  .filter(NSPredicate(format: "ANY children.x == 1"))

Problem:

But now, if I want to find all parents with children where x is 1 and y is 2, I don't understand how to write the query.

Say I create and persist two parents like this:

// Parent 1 should satisfy the query:
// It has 1 child that meets all criteria.
let parent1 = Parent()
parent1.name = "Parent 1"

let childOfParent1 = Child()
childOfParent1.x = 1
childOfParent1.y = 2
parent1.children.append(childOfParent1)

// Parent 2 should not satisfy the query:
// It has 2 children, each of which only partially meets the criteria.
let parent2 = Parent()
parent2.name = "Parent 2"

let child1OfParent2 = Child()
child1OfParent2.x = 1
child1OfParent2.y = -100 // Does not match criteria
parent2.children.append(child1OfParent2)

let child2OfParent2 = Child()
child2OfParent2.x = -100 // Does not match criteria
child2OfParent2.y = 2
parent2.children.append(child2OfParent2)

let realm = try! Realm()
try! realm.write {
  realm.add(parent1)
  realm.add(parent2)
}

Now, I can try to find only parents with children where x is 1 and y is 2 like this:

let results = realm
  .objects(Parent.self)
  .filter(NSPredicate(format: "ANY children.x == 1 && ANY children.y == 2"))
results.forEach { print($0.name) }

// This prints:
// Parent 1
// Parent 2

I want the query to return only Parent 1, but it returns both parents. The query matches any parents with children where x is 1, and also with children where y is 2, even if those are not the same children.

How would I rewrite my query so that it only matches parent 1?

Upvotes: 0

Views: 822

Answers (1)

Phlippie Bosman
Phlippie Bosman

Reputation: 5710

Ah, found it. The key is to use SUBQUERY.

A subquery is formatted as SUBQUERY(collection, itemName, query), and returns all sub-objects matching the query. So to find parents with children matching the query, you need to check the count of the resulting sub-objects.

In my example, the query would be:

let results = realm
  .objects(Parent.self)
  .filter(NSPredicate(format: "SUBQUERY(children, $child, $child.x == 1 && $child.y == 2).@count > 0"))

I.e fetch all Parent objects with more than 0 children matching the predicate x == 1 && y == 2.

Upvotes: 3

Related Questions