1

I'm having a problem fetching results from a Core Data store using predicates. What my application does is fetches results from the store based on one or more predicates and shows you either the results based on all of the predicates or the results based on each predicate.

It works fine for the most part, except I cannot get 'less than' queries to work. Here is the code I am using:

case CriteriaSelectionIsLessThan:
        pred = [NSPredicate predicateWithFormat:@"ANY value.attribute.name == %@ AND ANY value.%@ < %@", [attribute name], keyPath, [[activeValue value] value]];
        break;
case CriteriaSelectionIsMoreThan:
        pred = [NSPredicate predicateWithFormat:@"ANY value.attribute.name == %@ AND ANY value.%@ > %@", [attribute name], keyPath, [[activeValue value] value]];
        break;

The code for the 'more than' predicate works fine, it returns the data set for the query, such as > 1970 (a year). When I try to filter it using the 'less than' predicate, such as < 1970, it returns the entire Core Data dataset (unfiltered).

[[activeValue value] value] is an NSNumber.

I can't figure it out for the life of me - the predicates are exactly the same but with one character differing!

Help would be greatly appreciated. If you require any more code/information let me know.

Edit: here's how I map the data type on import from JSON:

[attrib setDataType:[NSNumber numberWithInteger:[[attribute valueForKey:@"type"] integerValue]]];...

// Set the correct data type of the value
switch ([[attrib dataType] integerValue]) {
    case AttributeDataTypeNumber:
        [value setNumberValue:[NSNumber numberWithInteger:[valueForKey integerValue]]];
        break;
    case AttributeDataTypeString:   
        [value setStringValue:(NSString *)valueForKey];
        break;
    case AttributeDataTypeDate: {
        NSDateFormatter *df = [[NSDateFormatter alloc] init];
        [df setDateFormat:@"dd-MM-YYYY"];
        [value setDateValue:[df dateFromString:(NSString *)valueForKey]];
    }
        break;
    case AttributeDataTypeBool:
        [value setBooleanValue:[NSNumber numberWithBool:[valueForKey boolValue]]];
        break;
}

See, the data type in my JSON is guaranteed to be right as I check that before importing it (not in the app). Even when I build a predicate that explicitly says that I want a certain dataType (such as value.attribute.dataType == 1 with 1 being a number in a enum) it still doesn't work. It's really bizzarre.

John Rogers
  • 2,192
  • 19
  • 29
  • 2
    Not sure if this would help but if it's possible to get the entirety of your keypath, not just what follows value, into a single expression (which would evaluate to value.RESTOFKEYPATH) and then use a `%K` as the format specifier, that would be interesting to check, but unlikely. That's the first thing that jumps out at me anyway. Something like even `[NSPredicate predicateWithFormat:@"ANY value.attribute.name == %@ AND ANY %K > %@", [attribute name], [NSString stringWithFormat:@"value.%@",keyPath], [[activeValue value] value]]` – Carl Veazey Oct 05 '12 at 05:53
  • Doesn't work either unfortunately! I have tried creating a predicate without any dynamic variables (i.e. explicitly typing the expression) and it still doesn't work. Works for more than, but not less than. I've checked to see if the data is being imported into Core Data fine - it is. I just can't pin down what the issue here is :( – John Rogers Oct 05 '12 at 06:10
  • Very strange! What do you see when you print the predicate's descriptions in the debugger? Also, Does it evaluate to YES when evaluated with a specific instance of an object for which you know it should be false - i.e. does it happen outside of the fetch request context? And you hard-coded the value name part as well with same results? Another weird idea I had was to break them into two predicates, one for name one for comparison, and rebuild a compound 'and' predicate. There is no logical reason that should work though so feel free to ignore that suggestion :) – Carl Veazey Oct 05 '12 at 06:53

2 Answers2

1

Are you sure it's returning the WHOLE dataset? I'm going to guess that the values you are getting for the lessThan operation are objects with NULL values for value.%@

Edit:

Here's how I deal with the model's attributes dynamically:

for (NSEntityDescription *entity in [self.managedObjectModel entities]) // self.managedObjectModel is a NSManagedObjectModel
{
    for (NSAttributeDescription *attribute in [[entity attributesByName] allValues])
    {
        NSString *key = [attribute name];
        // do something with key
        switch ([attribute attributeType])
        {
            case NSBooleanAttributeType:
            case NSInteger16AttributeType:
            case NSInteger32AttributeType:
            case NSInteger64AttributeType:
            case NSDecimalAttributeType:
            case NSDoubleAttributeType:
            case NSFloatAttributeType:
                // do something with numeric types
                break;
            case NSStringAttributeType:
                // do something with string types
                break;
            case NSDateAttributeType:
                // do something with date types
                break;
         }
    }
}

Each case handles predicates differently. You can cache the key:type relationships for performance.

John Estropia
  • 17,460
  • 4
  • 46
  • 50
  • You could be onto something here. The reason I had to do it like this, is that each value can be of a different data type (string, date, number). At first, I tried this with a Transient property, however when putting together NSPredicates I was having trouble with NSNumbers (it wasn't casting them properly and was returning incorrect results) - something to do with SQLite storing everything as binary. Because of this, each value has a 'stringValue', 'numberValue'... etc property. When they aren't of that type, the other values are nil. I may have to check the dataType of the object. – John Rogers Oct 05 '12 at 07:52
  • Yeah this problem looked familiar. I also wrote a dynamic search builder like this and nil results makes a mess of things. That's because `[NSObject valueForKey:]` returns nil if the object doesn't have the key. You can include a `AND value.%@ != nil` in your predicate, but that will also exclude objects with valid nil values for some keyPaths. Or you can map all of the known keyPaths to their types, then build our NSPredicates accordingly – John Estropia Oct 05 '12 at 08:47
  • I think this is the culprit. For now, I have fixed it by explicitly setting the numberValue attribute on values that are not a number to 9 trillion or something (I'll have to come back to it later). Your answer is correct so I'll mark it so. How did you end up fixing your problem? Did you have to re-do the database design? – John Rogers Oct 09 '12 at 04:15
  • I added a snippet that I use for this. Never needed to redesign the database. – John Estropia Oct 09 '12 at 04:47
  • Btw, you might misunderstand. The looping code up there is not called when building predicates. It's for mapping entity attribute names to their types. Then when it's time to build predicates, create a predicate that's right for the attribute's type. – John Estropia Oct 09 '12 at 05:04
  • I already do something like that, that's why it's so confusing! See my edit in my question. – John Rogers Oct 09 '12 at 05:50
  • Your second code should be correct in itself but the problem is you should – John Estropia Oct 09 '12 at 06:22
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/17735/discussion-between-john-rogers-and-erurainon) – John Rogers Oct 09 '12 at 07:00
0

I figured it out! If anyone else has run into this problem, here's how I fixed it:

[NSPredicate predicateWithFormat:@"SUBQUERY(value, $val, $val.attribute.name == %@ && $val.%@ < %@).@count > 0", [attribute name], keyPath, [[activeValue value] value]]

I think it's because it's a to-many key that has to be matched on more than one criteria. When I was using the ANY keyword, I think this was the culprit for returning the values I didn't want. Works like a charm now.

John Rogers
  • 2,192
  • 19
  • 29