开发者

How to use the "ALL" aggregate operation in a NSPredicate to filter a CoreData-based collection

Based on the data mode开发者_运维技巧l below

How to use the "ALL" aggregate operation in a NSPredicate to filter a CoreData-based collection

And based on user input I create a NSSet of managedObjects of entity Tag called selectedTags.


My problem:

[NSPredicate predicateWithFormat:@"ANY entryTags IN %@", selectedTags];

... this will return any Entry with at least one entryTag that is in the selectedTags set.

I want something along the lines of:

[NSPredicate predicateWithFormat:@"ALL entryTags IN %@", selectedTags];

... notice the only change is the "ANY" to "ALL". This illustrates what I want, but does not work.

To formulate the outcome I expect:

I'm looking for a solution that will return only Entries who's entryTags are all in the selectedTags list (but at the same time, if possible, not necessarily the other way around).

To further illustrate:

(tag)Mom

(tag)Dad

(tag)Gifts

(entry)she is a she.....(tag)mom

(entry)he is a he........(tag)dad

(entry)gifts for mom...(tags:)mom, gifts

(entry)gifts for dad.....(tags:)dad, gifts

If selectedTags contains "mom" and "gifts", then the entry "gifts for dad" will show up, since it has the tag "gifts". I'd rather have it not show :)


This is the definite answer so far:

[NSPredicate predicateWithFormat:@"SUBQUERY(entryTags, $tag, $tag IN %@).@count = %d", selectedTags, [selectedTags count]];

B-E-A-U-T-I-F-U-L.

Thanks to Dave DeLong.


How about using a compound predicate? As I understand you want to return all Entries that match a list of tags not just any of them. One approach would be to create a predicate for each tag, then AND them together using a compound predicate.

NSMutableArray *predicates = [[NSMutableArray alloc] init];
for (Tag *tag in selectedTags) {
    [predicates addObject:[NSPredicate predicateWithFormat:@"ANY entryTags.tagName MATCHES %@", tag.tagName]];
}
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];

This should achieve want you want. Then just set this predicate on your request.


You can't do what you want with a predicate.

The ANY and ALL operators apply to the entity being tested (in this case Entry) and not the contents of the collection (selectedTags). Either operator will return an Entry object that matches any single element of the collection. The ANY operator will return the first match it finds while the ALL operator will return all matches. In neither case will they return an entry that matches every element in the provided collection.

(It also looks like you are trying to use actual Tag objects in selectedTags. That will most likely not work either because object compares on classes without dedicated comparison methods usually fail. It is also slow. You need to compare attributes in predicates.)

Since you already have the Tag objects you want, to find the candidate related Entity objects, you just have to walk the Tag.taggedEntries relationship. Then you have to find the intersection of all the sets of Entity object to find only those Entity objects that are related to every selected Tag bject. Since there isn't an intersect collections operator, you need a loop.

if ([selectedEntries count]>=2) {
    NSMutableSet *intersectEntries=[[NSMutableSet alloc] initWithCapacity:1];
    for (int i=1; i<[selectedTags count]; i++) {
        if ([intersectEntries count]==0) {            
            [intersectEntries unionSet:[[selectedEntries objectAtIndex:(i-1)] valueForKey:@"taggedEntries"]];
        }        
        [intersectEntries intersectSet:[[selectedEntries objectAtIndex:i] valueForKey:@"taggedEntries"]];
    }
}

(Note: I didn't test this but it should work.)

Now intersectEntries should contain only those Entry objects that are related to every selected tag.


I realized I could give something back here for the guidance that I have previously gotten. By using the code TechZen supplied I was able to come up with the following -- and for me highly valued -- piece of code:

- (NSArray *)unionSetOfObjectsForObjects:(NSArray *)objects {

    NSMutableSet *unionSetOfObjects = [NSMutableSet set];

    if (objects.count)
        [unionSetOfObjects unionSet:[[objects objectAtIndex:0] valueForKey:@"key"]];
       //unionSetOfObjects = [[objects objectAtIndex:0] valueForKey:@"key"];

    if (objects.count > 1)
        for (id object in objects)
            [unionSetOfObjects intersectSet:[object valueForKey:@"key"]];

    return unionSetOfObjects.allObjects;
}

If it is not immediately obvious what this code does:

It collects all the values (in my case objects) for the key key on all of the objects provided in the objects array.

This code just... tastes good, doesn't it?


The simplest way to do this:

[NSPredicate predicateWithFormat:@"entryTags IN %@", selectedTags];

You don't need the ALL clause. It's also documented here:

Predicate Programming guide

And as you can see in this post the user does it successfully (read the comments to the original question)

NSPredicate iPhone 3.2 SDK Core Data “IN clause”...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜