Rob
Rob

Reputation: 3

CoreData Many-to-Many Find Duplicates

I am working on a predicate to search for users with the same preferences as any other user

Users <<->> Preferences

Given the following state; I wish to check for a new registering user

(Sam, nickname: "foo", language: "english")

to see if any existing users have the same preferences. There can be any number of preference types. In this case it would detect that Sally has the same selections

Users

| Name  |
| Sally |
| John  |

PreferenceTypes

| Name         |
|  nickname    |
|  language    |
|   ....       |

UserPreferences

User       PreferenceTypes      Selection
Sally  |   nickname         |     "foo"
Sally  |   language         |     "english"
John   |   nickname         |     "bar"
John   |   language         |     "english"

Upvotes: 0

Views: 96

Answers (2)

Mundi
Mundi

Reputation: 80265

This is how you could set up the model:

User <---->> Preference <<-----> PreferenceType

User.name                  // name string
User.preferences           // to-many to Preference

Preference.value           // the value, or "selection"
Preference.user            // to-one to User
Preference.type            // to-one to PreferenceType

Preference.name            // name of preference, such as "language" 
PreferenceType.preferences // to-many relationship to Preference

Your predicate will need a subquery and will be quite "expensive" in terms of resource usage. I assume that it is allowed that some users have preferences that others do not have, i.e. while we do know the total number of possible preferences, we do not know the number of preferences one particular user has. I cannot write that query from the top of my head, I would have to think about it. --- Anyone?

One practical approach would be to first fetch a subset of users, say, those with the exact same nickname. It would be simpler to just fetch preference objects with a predicate like this:

[NSPredicate predicateWithFormat:
   @"type.name = 'nickname' && value = %@ && not (user = %@)", userNickname, user];

On the small subset of users (referenced by foundPreference.user) you could run a custom method you write (hasSamePreferences) where you can iterate through the preferences and check for existence and equality.

-(BOOL)hasSamePreferencesAsUser:(User*)otherUser {
    NSSet *myPreferenceKeys = [self.preferences valueForKeyPath:@"type.name"];
    NSSet *otherPreferenceKeys = [otherUser.preferences valueForKeyPath:@"type.name"];
    if (myPreferenceKeys.count != otherPreferenceKeys.count) {
       return NO;
    }
    for (NSString *key in myPreferenceKeys) {
       if (![otherPreferenceKeys containsObject:key]) {
          return NO;
       }
       NSPredicate *keyFilter = [NSPredicate predicateWithFormat:@"type.name = %@", key];
       // String only for demonstration
       NSString *myValue = [self.preferences 
            filteredArrayUsingPredicate:keyFilter].firstObject;
       NSString *otherValue = [otherUser.preferences 
            filteredArrayUsingPredicate:keyFilter].firstObject;
       if (![myValue isEqualToString:otherValue]) {
          return NO;
       }
    }
    return YES;
}

BTW, if you do not expect more than, say, 20 preferences, it would be even more pragmatic and hardcode these preferences as properly named attributes to User (or a Settings entity with a one-to-one relationship if you prefer). Your code will be very readable, and your predicates fairly trivial.

Upvotes: 1

geo
geo

Reputation: 1791

If you want to check if the nickname already exists, you can try something like this (if I unterstood your model correct)

NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:@"UserPreferences"
                                    inManagedObjectContext:context]];
[NSPredicate predicateWithFormat:@"Selection == %@ AND PreferenceTypes == 'nickname'", newUser.nickname]
NSArray *ary = [context executeFetchRequest:fetchRequest error:&err];

if(ary && ary.count > 0)
{
    // handle "user exists" case
}
else
{
    // handle "create user" case
}

I hope that was what you was looking for. If I understood your question wrong or it doesn't work, let me know ;)

Upvotes: 0

Related Questions