5

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

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

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

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 如果我尝试获取错误“Unsupported function expression count:(dayString)”。我认为“dayString.@count”中的 dayString 甚至在上面的代码中都不重要......但是,为了清楚起见,我把它放在了里面(SQL 计数只对分组的行进行操作)。

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

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

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

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 相同的效率进行等效类型的操作。

4

3 回答 3

7

对的,这是可能的。您不能将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

于 2016-07-11T18:25:35.773 回答
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"];

但这与您使用的并没有太大的不同。

于 2014-04-16T22:13:45.577 回答
0

在 Core Data 中查找重复项的最佳方法取决于您的数据。根据Efficiently Importing Data并假设您必须导入少于 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];
于 2015-06-05T13:15:40.240 回答