Ios 是否可以在havingPredicate中使用group by count来获取CoreData(用于重复检测)?

Ios 是否可以在havingPredicate中使用group by count来获取CoreData(用于重复检测)?,ios,core-data,nsexpression,Ios,Core Data,Nsexpression,作为参考,我试图解决的问题是有效地查找和删除表中可能包含大量条目的重复项 我正在使用的表名为PersistedDay,其中包含dayString对象(它是一个字符串。:-p)。还有更多的专栏与这个问题无关。我想找到任何有重复项的持续日期 在SQL中,这是一种有效的方法(仅供参考,我可以在支持SQLite DB的CoreData上执行此查询): 这将仅返回具有重复项的DayString,然后您可以通过使用生成的DayString进行查询来获取这些对象的所有字段(您可以将其用作子查询,以便在一个请求

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

我正在使用的表名为PersistedDay,其中包含dayString对象(它是一个字符串。:-p)。还有更多的专栏与这个问题无关。我想找到任何有重复项的持续日期

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

这将仅返回具有重复项的DayString,然后您可以通过使用生成的DayString进行查询来获取这些对象的所有字段(您可以将其用作子查询,以便在一个请求中完成所有操作)

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 count只对分组行进行操作)

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

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

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

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

我能想到的最好方法是:

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"];

但这与您使用的并没有太大区别。

在核心数据中查找重复项的最佳方法取决于您的数据。根据并假设您必须导入少于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 */
    }
}
为了提高速度,可以在核心数据中索引日字符串

如果您记得上次重复清理的时间戳或日期,还可以减少数据集的大小:

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

是的,这是可能的。不能将
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)
填写游乐场档案,网址:

我猜答案与在NSPredicate中使用子查询有关。你试过了吗?看看,它似乎有一些想法:)我不确定子查询在这里能帮我什么忙。除非我可以用一个包含count的having子句进行分组的子查询。;-)您也可以使用同一个表进行连接以检测重复,但我也不清楚如何使用核心数据进行连接。关于那个网站,防止我熟悉的欺骗。这里的问题是清理现有的复制。看起来他的解决方案是像我上面所做的那样(在查询结果中包括计数)。这仍然很糟糕,因为如果表很大,它将返回很多结果。谢谢你!是的,仔细看,我也看不出子查询有什么帮助。仅仅是玩弄它,我看不出有什么理由使用havingPredicate,因为它似乎是实现的。我能想到的最好的解决方案如下,但它仍然会导致字典中每个唯一键都有一个字典。这不是一个完整的记录,但可以想象它仍然有很多记忆。你可以通过使用FETCHBATCHEXSIZE和A.I.In循环来获得更好的结果。如果它真的是一个问题,你可能会考虑使用SQLite直接构建剪枝的重复列表,然后返回到核心数据来修剪它们。这真的不是一个理想的解决方案,而且很明显在将来有可能会失败。是的,如果我没有得到更好的答案,我会接受这个答案。过滤比for循环好一点。我不太清楚为什么havingPredicate真的存在,因为我找不到任何情况下它的效果与只使用predicate不同。除了文档之外,我甚至没有在网上发现太多关于它的提及。我接受你的回答。也许将来会有人能找到某种方法来进行更有效的查询,但现在我将采用这种方法-PThanks,但这似乎比使用groupby效率低,因为您可以对count>1使用整数检查,而不是对每个条目进行字符串比较。我肯定会有超过1000个条目。我明白了。这基本上看起来和我想做的完全一样,只是表达式作为一个对象以格式字符串的形式插入,而不是使用键路径。嗯,用swift,因为我最初写这篇文章时它并不存在-P谢谢你的操场…我不容易获得原始代码。似乎有效,因此我将接受这一正确答案。我很好奇,苹果是在我写这篇文章后才修复了这个行为,还是我只是语法错误。哦,好吧-P@stuckj我也有Objective-C版本,但它更容易在Swift操场上运行和测试。我认为操场上运行着一些iOS 8模拟人生,所以这个技巧在当时也应该起作用。
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)