是否可以在havingPredicate中使用group by进行CoreData获取(用于dupe检测)?

时间:2014-04-16 17:51:14

标签: ios core-data nsexpression

作为参考,我试图解决的问题是有效地查找和删除可能包含大量条目的表中的重复项。

我正在使用的表名为PersistedDay,其中包含一个dayString对象(它是一个字符串。:-P)。还有更多列与此问题无关。我想找到任何有重复的PersistedDay。

在SQL中,这是您可以这样做的有效方法之一(仅供参考,我可以在CoreData支持SQLite DB上执行此查询):

SELECT ZDAYSTRING FROM ZPERSISTEDDAY GROUP BY ZDAYSTRING HAVING COUNT(ZDAYSTRING) > 1;

这只返回具有重复项的dayStrings,然后您可以通过使用生成的日期字符串查询来获取这些对象的所有字段(您可以将其用作子查询以在一个请求中完成所有操作)。

NSFetchRequest似乎也拥有所有必需的部分,但它似乎并没有起作用。这就是我试图做的事情:

NSManagedObjectContext *context = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"PersistedDay" inManagedObjectContext:context];
[request setEntity:entity];

NSPropertyDescription* dayStringProperty = entity.propertiesByName[@"dayString"];

request.propertiesToFetch = @[dayStringProperty];
request.propertiesToGroupBy = @[dayStringProperty];
request.havingPredicate = [NSPredicate predicateWithFormat: @"dayString.@count > 1"];
request.resultType = NSDictionaryResultType;

NSArray *results = [context executeFetchRequest:request error:NULL];

这不起作用。 :-P如果我尝试我得到一个错误"不支持的函数表达式计数:(dayString)"在尝试进行获取时。我不认为" dayString中的dayString。@ count"甚至在上面的代码中也很重要...但是,为了清楚起见,我把它放进去(SQL计数只对分组的行进行操作)。

所以,我的问题是:这是可能的,如果是的话,这样做的语法是什么?我无法在CoreData文档中找到任何内容来指示如何执行此操作。

我发现了一个类似的SO帖子,我现在不幸再也找不到了关于在一个条款中运行计数(我不认为有一个小组)。但是,海报放弃了,并在找不到解决方案之后采取了不同的方式。我希望这更明确,所以也许有人有答案。 :)

作为参考,这就是我现在正在做的事情,它可以工作,但需要返回几乎所有的行,因为在大多数情况下只有很少的重复:

NSManagedObjectContext *context = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"PersistedDay"
                                          inManagedObjectContext:context];
[request setEntity:entity];

NSPropertyDescription* dayStringProperty = entity.propertiesByName[@"dayString"];

// Get the count of dayString...
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: @"dayString"]; // Does not really matter
NSExpression *countExpression = [NSExpression expressionForFunction: @"count:" arguments: [NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: @"dayStringCount"];
[expressionDescription setExpression: countExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];

request.propertiesToFetch = @[dayStringProperty, expressionDescription];
request.propertiesToGroupBy = @[dayStringProperty];
request.resultType = NSDictionaryResultType;

NSArray *results = [context executeFetchRequest:request error:NULL];

然后我必须循环结果并且仅返回具有dayStringCount>的结果。 1. having子句应该做什么。 :-P

注意:我知道CoreData不是SQL。 :)我想知道我是否能以与SQL相同的效率执行等效类型的操作。

3 个答案:

答案 0 :(得分:6)

是的,这是可能的。您不能将count作为关键路径引用,但可以将其作为变量引用。就像在SQL中一样。在我的例子中,我创建了具有重复名称的城市。

let fetchRequest = NSFetchRequest(entityName: "City")

let nameExpr = NSExpression(forKeyPath: "name")
let countExpr = NSExpressionDescription()
let countVariableExpr = NSExpression(forVariable: "count")

countExpr.name = "count"
countExpr.expression = NSExpression(forFunction: "count:", arguments: [ nameExpr ])
countExpr.expressionResultType = .Integer64AttributeType

fetchRequest.resultType = .DictionaryResultType
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "name", ascending: true) ]
fetchRequest.propertiesToGroupBy = [ cityEntity.propertiesByName["name"]! ]
fetchRequest.propertiesToFetch = [ cityEntity.propertiesByName["name"]!, countExpr ]

// filter out group result and return only groups that have duplicates
fetchRequest.havingPredicate = NSPredicate(format: "%@ > 1", countVariableExpr)

完成游乐场档案: https://gist.github.com/pronebird/cca9777af004e9c91f9cd36c23cc821c

答案 1 :(得分:5)

我能想到的最好的是:

NSError*                error;

NSManagedObjectContext* context = self.managedObjectContext;
NSEntityDescription*    entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:context];

// Construct a count group field
NSExpressionDescription*    count = [NSExpressionDescription new];
count.name = @"count";
count.expression = [NSExpression expressionWithFormat:@"count:(value)"];
count.expressionResultType = NSInteger64AttributeType;

// Get list of all "value" fields (only)
NSPropertyDescription*  value = [entity propertiesByName][@"value"];

NSFetchRequest*         request = [[NSFetchRequest alloc] initWithEntityName:@"Event"];
request.propertiesToFetch = @[ value, count];
request.propertiesToGroupBy = @[ value ];
request.resultType = NSDictionaryResultType;
NSArray*                values = [context executeFetchRequest:request error:&error];

// Filter count > 1
values = [values filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"count > 1"]];

// slice to get just the values
values = [values valueForKeyPath:@"value"];

但这与你正在使用的东西并没有太大的不同。

答案 2 :(得分:0)

在核心数据中查找重复项的最佳方法取决于您的数据。根据{{​​3}}并假设您必须导入少于1000个PersistedDays,我建议使用此解决方案:

NSFetchRequest* fetchRequest = [NSFetchRequest new];

[fetchRequest setEntity:[NSEntityDescription entityForName:@"PersistedDay" inManagedObjectContext:myMOC]];
[fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"dayString" ascending:NO]]];

NSArray* persistedDays = [myMOC executeFetchRequest:fetchRequest error:nil];

for (NSUInteger i = persistedDays.count - 1; i > 0; --i) {

    PersistedDay *currentDay = persistedDays[i];
    PersistedDay *nextDay = persistedDays[i-1];

    if ([currentDay.dayString isEqualToString:nextDay.dayString]) {
        /* Do stuff/delete with currentDay */
    }
}

为了加速,可以在Core Data中索引dayString。

如果您记得时间戳或最后一次重复清理的日期,您也可以减少数据集:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"importDate > %@", lastDuplicateCleanUp];