When using CoreData, the next multi-column index predicate is very slow - it takes almost 2 seconds for 26,000 records.
Note that both columns are indexed, and I purposefully execute the query with> and <= instead of starting to do it quickly:
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"airportNameUppercase >= %@ AND airportNameUppercase < %@ \ OR cityUppercase >= %@ AND cityUppercase < %@ \ upperText, upperTextIncremented, upperText, upperTextIncremented];
However, if I run two separate fetchRequests, one for each column, and then combined the results, then each fetchRequest takes only 1-2 hundredths of a second, and combining lists (which are sorted) takes about 1 / 10th of a second.
Is this a bug in how CoreData handles multiple indexes or is this the expected behavior? Below is my complete, optimized code that works very fast:
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init]autorelease]; [fetchRequest setFetchBatchSize:15]; // looking up a list of Airports NSEntityDescription *entity = [NSEntityDescription entityForName:@"Airport" inManagedObjectContext:context]; [fetchRequest setEntity:entity]; // sort by uppercase name NSSortDescriptor *nameSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"airportNameUppercase" ascending:YES selector:@selector(compare:)] autorelease]; NSArray *sortDescriptors = [[[NSArray alloc] initWithObjects:nameSortDescriptor, nil]autorelease]; [fetchRequest setSortDescriptors:sortDescriptors]; // use > and <= to do a prefix search that ignores locale and unicode, // because it very fast NSString *upperText = [text uppercaseString]; unichar c = [upperText characterAtIndex:[text length]-1]; c++; NSString *modName = [[upperText substringToIndex:[text length]-1] stringByAppendingString:[NSString stringWithCharacters:&c length:1]]; // for the first fetch, we look up names and codes // we'll merge these results with the next fetch for city name // because looking up by name and city at the same time is slow NSPredicate *predicate = [NSPredicate predicateWithFormat: @"airportNameUppercase >= %@ AND airportNameUppercase < %@ \ OR iata == %@ \ OR icao == %@", upperText, modName, upperText, upperText, upperText]; [fetchRequest setPredicate:predicate]; NSArray *nameArray = [context executeFetchRequest:fetchRequest error:nil]; // now that we looked up all airports with names beginning with the prefix // look up airports with cities beginning with the prefix, so we can merge the lists predicate = [NSPredicate predicateWithFormat: @"cityUppercase >= %@ AND cityUppercase < %@", upperText, modName]; [fetchRequest setPredicate:predicate]; NSArray *cityArray = [context executeFetchRequest:fetchRequest error:nil]; // now we merge the arrays NSMutableArray *combinedArray = [NSMutableArray arrayWithCapacity:[cityArray count]+[nameArray count]]; int cityIndex = 0; int nameIndex = 0; while( cityIndex < [cityArray count] || nameIndex < [nameArray count]) { if (cityIndex >= [cityArray count]) { [combinedArray addObject:[nameArray objectAtIndex:nameIndex]]; nameIndex++; } else if (nameIndex >= [nameArray count]) { [combinedArray addObject:[cityArray objectAtIndex:cityIndex]]; cityIndex++; } else if ([[[cityArray objectAtIndex:cityIndex]airportNameUppercase] isEqualToString: [[nameArray objectAtIndex:nameIndex]airportNameUppercase]]) { [combinedArray addObject:[cityArray objectAtIndex:cityIndex]]; cityIndex++; nameIndex++; } else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] < [[nameArray objectAtIndex:nameIndex]airportNameUppercase]) { [combinedArray addObject:[cityArray objectAtIndex:cityIndex]]; cityIndex++; } else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] > [[nameArray objectAtIndex:nameIndex]airportNameUppercase]) { [combinedArray addObject:[nameArray objectAtIndex:nameIndex]]; nameIndex++; } } self.airportList = combinedArray;
source share