NSPredicate Inside a SUBQUERY
Asked Answered
R

2

6

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)
Reinke answered 19/4, 2016 at 13:49 Comment(8)
could you post your Order class/definition and explain what you want to achieve?Stefanistefania
In the workaround you don't need a subquery, ANY orders in %@ will do. Why do you set returnObjectsAsFaults to false?Frangible
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.Frangible
Thanks for the suggestions. @Stefanistefania 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.Reinke
@Frangible 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 suggestionsReinke
@Willeke, you say it's possible to convert the predicate to be like $x.whatever, how can I do this?Reinke
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.Frangible
@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?Havens
R
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")
Reinke answered 22/4, 2016 at 15:4 Comment(0)
B
5

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)
Brattice answered 10/3, 2020 at 14:43 Comment(0)
R
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")
Reinke answered 22/4, 2016 at 15:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.