4

I'm Trying to create a NSPredicate what can do the sum of two properties and compare it to another property of an object:

Object.property1 + Object.property2 < Object.property3

I tried (%K + %K) < %K but with no success.

PS: Should work even some of properties are null.

I'm trying to avoid making a lot of compound predicates.

Thanks, Florin

FlorinD
  • 481
  • 2
  • 8
  • 25
  • I think it would be much easier if you add a computed property: `@property (nonatomic, assign), NSIntegerOrDoubleOrWhatever property4;` `-(NSIntegerOrDoubleOrWhatever)property4 {return self.property1 + self.property2;}`, then the format is `[NSPredicate predicateWithFormat:@"%K < %@", @"property4", @(property3)]` – Larme Feb 07 '18 at 15:41
  • This will be optimal but my current code structure does not supports that , it's a Core Data fetch what i have to do . – FlorinD Feb 07 '18 at 18:14
  • https://stackoverflow.com/questions/34063965/nsmanagedobject-subclass-property-in-category You should be able to do so. It's been a while since I played with CoreData, but the answer seems legit. – Larme Feb 07 '18 at 18:17
  • You suggest to use Transient property, i already tried this but Apple says : "You cannot fetch using a predicate based on transient properties (although you can use transient properties to filter in memory yourself)." "Core Data Programming Guide" – FlorinD Feb 07 '18 at 18:47
  • 1
    `[NSPredicate predicateWithFormat:@"(%K + %K) < %K", prop1Name, prop2Name, prop3Name]` should work – is that what you tried? – Martin R Feb 15 '18 at 12:40
  • @MartinR Exactly this but is fails when one or more properties are nil. – FlorinD Feb 15 '18 at 12:45
  • @FlorinDobjenschi: What do you expect for objects with nil properties? Should they be included in the result set or not? – Martin R Feb 15 '18 at 12:51
  • @MartinR I want only objects that respect (%K + %K) < %K", prop1Name, prop2Name, prop3Name. One or more properties(prop1Name,prop2Name) may be nil... Thanks for trying to help me. – FlorinD Feb 15 '18 at 13:14
  • Unless I am mistaken, the predicate `[NSPredicate predicateWithFormat:@"(%K + %K) < %K", @"a", @"b", @"c"]` returns all objects for which all three properties a, b, c have a value, *and* those values satisfy `a + b < c`. – Is that what you are looking for? – Martin R Feb 15 '18 at 13:20
  • example of Object: Object.prop1 = 1 Object.prop2 = null Object.prop3 = 3 When appling (%K + %K) < %K", prop1Name, prop2Name, prop3Name the request should return above object but is not in the response if i make prop2 = 0 instead of nil the request is returning the object above. Currently my db has null values on some objects for .prop1 and .prop2 . – FlorinD Feb 15 '18 at 13:24
  • So you want undefined (null) values treated as zero? – Martin R Feb 15 '18 at 13:30
  • Yep, if possible :) – FlorinD Feb 15 '18 at 13:32

2 Answers2

3

Assuming you are using a SQLite persistent store, the fetch predicate is converted into an equivalent SQLite WHERE clause. The handling of NULL values is therefore determined by SQLite. From the SQLite documentation:

Adding anything to null gives null

so if any of the attributes in your predicate is NULL, the sum will be NULL. The Predicate Programming Guide is also explicit that NULL values must be tested separately:

A comparison predicate does not match any value with null except null (nil) or the NSNull null value .... If you want to match null values, you must include a specific test in addition to other comparisons ...

You are therefore faced with having to explicitly test for null values in your predicate, as @Kamil.S has done in his answer. There is one way to make it slightly easier to read and handle: replace the property values with a conditional expression (equivalent to property1 == nil ? 0 : property1). You can do this with NSExpression (see documentation):

NSExpression *prop1 = [NSExpression expressionForConditional:[NSPredicate predicateWithFormat:@"property1 == nil"] 
                                    trueExpression: [NSExpression expressionForConstantValue: @0] 
                                    falseExpression: [NSExpression expressionForKeyPath:@"property1"]];

(and similarly for property2, etc). You can then substitute these NSExpression values into your predicate using the %@ format specifier:

fetch.predicate = [NSPredicate predicateWithFormat:@"%@ + %@ < %@", prop1, prop2, prop3];

CoreData parses the conditional expression into a SQLite CASE (...) when 1 then (...) else (...) END clause which is evaluated in the database as part of the fetch: but I have no idea how performant that will be compared to the lengthy compound predicate. (I have also only tested using a single conditional expression; I assume CoreData can handle two or more in a single predicate, but leave it for you to confirm.)

pbasdf
  • 21,386
  • 4
  • 43
  • 75
  • Thank you , this is what i looking for, as i said in the first post to avoid making a lot of compound predicates. I changed the code i run the tests and is passing . – FlorinD Feb 19 '18 at 09:03
0
NSPredicate *p = [NSPredicate predicateWithFormat:
                  @"(%K==nil AND %K==nil AND %K!=nil AND 0 < %K) OR"
                   "(%K==nil AND %K==nil AND %K!=nil AND %K < 0) OR"
                   "(%K==nil AND %K==nil AND %K!=nil AND %K < 0) OR"

                   "(%K==nil AND %K!=nil AND %K!=nil AND %K < %K) OR"
                   "(%K==nil AND %K!=nil AND %K!=nil AND %K < %K) OR"
                   "(%K==nil AND %K!=nil AND %K!=nil AND (%K + %K) < 0) OR"

                   "(%K!=nil AND %K!=nil AND %K!=nil AND (%K + %K < %K))",

                   @"property1",@"property2",@"property3",@"property3",
                   @"property3",@"property1",@"property2",@"property2",
                   @"property3",@"property2",@"property1",@"property1",

                   @"property1",@"property2",@"property3",@"property2",@"property3",
                   @"property2",@"property1",@"property3",@"property1",@"property3",
                   @"property3",@"property1",@"property2",@"property1",@"property2",

                   @"property1",@"property2",@"property3",@"property1",@"property2",@"property3"
                   ];
Kamil.S
  • 5,205
  • 2
  • 22
  • 51
  • Thanks for trying to help me but seems predicateWithBlock can not be used on core data , i receive an error : CoreData: error: exception handling request: , Problem with subpredicate BLOCKPREDICATE(0x10c5638c0) with userInfo of (null) . Also more info in here : https://stackoverflow.com/questions/3543208/nsfetchrequest-and-predicatewithblock – FlorinD Feb 15 '18 at 15:23
  • Edited my answer with new approach – Kamil.S Feb 15 '18 at 21:25