5

I'm trying to write a SUBQUERY for an NSPredicate. My problem is I don't have the query on hand for the predicate portion of the SUBQUERY. Is there a way to nest an NSPredicate inside the SUBQUERY?

For example this is what I have tried:

let ordersPredicate = NSPredicate()//some predicate passed in
//how do I use the ordersPredicate inside the subquery??
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x,'%@').@count > 0", ordersPredicate.predicateFormat)

Update: I had to do a dirty workaround for now by executing the first predicate and doing an ANY IN query. Its lame :( because I'm executing the ordersPredicate elsewhere as well later in the pipeline..

let fetchRequest = NSFetchRequest()//blah
fetchRequest.predicate = orderPredicate
let orders = //execute fetch
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, ANY $x in %@).@count > 0", orders)
Nathan Hart
  • 336
  • 2
  • 13
  • could you post your Order class/definition and explain what you want to achieve? – vikingosegundo Apr 19 '16 at 20:44
  • In the workaround you don't need a subquery, `ANY orders in %@` will do. Why do you set `returnObjectsAsFaults` to false? – Willeke Apr 20 '16 at 11:36
  • If you want to use a predicate in a subquery, the predicate should be something like `$x.orderNumber = 10`, with`$x`. It is possible to convert the predicate but might be easier to redesign the pipeline. – Willeke Apr 20 '16 at 11:42
  • Thanks for the suggestions. @vikingosegundo I'm trying to Query a Branch Entity which has a toMany relationships to orders. Basically I want to query for any branches inside a predicate for Orders. – Nathan Hart Apr 20 '16 at 12:41
  • @Willeke I copied that code, but in context it doesn't make sense to set returnObjectsAsFalse. I'm looking into redesigning some things thanks for the suggestions – Nathan Hart Apr 20 '16 at 12:41
  • @Willeke, you say it's possible to convert the predicate to be like $x.whatever, how can I do this? – Nathan Hart Apr 21 '16 at 15:02
  • You have to build a copy of the predicate. Replace keypath expressions by an function expression with a variable expression, selector `valueForKeyPath:` and the keypath. – Willeke Apr 21 '16 at 16:29
  • @NathanHart Do you need to use a second fetch request? Could you just use the inverse relationship on the Order entity to achieve the same result? – pbasdf Apr 21 '16 at 16:52

2 Answers2

4

For anyone coming across this in the future, you don't need to build the predicate string yourself, just use predicateFormat directly in your subquery string. It is already properly escaped, so use string interpolation.

let ordersPredicate = NSPredicate()
// Add it to the query with string interpolation
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, \(ordersPredicate.predicateFormat)).@count > 0")

If you try to pass it as an argument, it will try to escape the string again, which will probably not work.

// Don't do this!
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, %@).@count > 0", ordersPredicate.predicateFormat)
Brojowski
  • 65
  • 7
3

Thanks to some pointers from @Willeke I was able to come up with a solution.

public extension NSPredicate{
    public func stringForSubQuery(prefix:String) -> String{
        var predicateString = ""
        if let predicate = self as? NSCompoundPredicate{
            for (index, subPredicate) in predicate.subpredicates.enumerate(){

                if let subPredicate = subPredicate as? NSComparisonPredicate{
                    predicateString = "\(predicateString) \(prefix)\(subPredicate.predicateFormat)"
                }
                else if let subPredicate = subPredicate as? NSCompoundPredicate{
                    predicateString = "\(predicateString) (\(subPredicate.stringForSubQuery(prefix)))"
                }
                //if its not the last predicate then append the operator string
                if index < predicate.subpredicates.count - 1 {
                    predicateString = "\(predicateString) \(getPredicateOperatorString(predicate.compoundPredicateType))"
                }
            }
        }
        return predicateString
    }
    private func getPredicateOperatorString(predicateType: NSCompoundPredicateType) -> String{
        switch(predicateType){
        case .NotPredicateType: return "!"
        case .AndPredicateType: return "&&"
        case .OrPredicateType: return "||"
        }
    }
}

And here is the usage

let ordersPredicate = NSPredicate()//some predicate passed in
let ordersPredicateFormat = orderPredicate.stringForSubQuery("$x.")
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, \(ordersPredicateFormat)).@count > 0")
Nathan Hart
  • 336
  • 2
  • 13