2014-04-16 9 views
5

Dla porównania, problemem, który próbuję rozwiązać, jest skuteczne znajdowanie i usuwanie duplikatów w tabeli, która może zawierać wiele wpisów.Czy możliwe jest użycie grupy według liczenia w module havingPredicate w celu pobrania CoreData (w celu wykrycia dupe)?

Tabela, nad którą pracuję nazywa się PersistedDay z obiektem dayString (jest to ciąg. :-P). Więcej kolumn nie ma związku z tym pytaniem. Chciałbym znaleźć jakiekolwiek PerscedDay, które mają duplikaty.

W SQL, jest jednym z efektywnych sposobów można to zrobić (FYI, mogę zrobić to zapytanie na CoreData podkład SQLite DB):

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

ta zwraca tylko dayStrings które mają duplikaty i możesz następnie uzyskać wszystkie pola dla tych obiektów, wysyłając zapytanie za pomocą wynikowych łańcuchów dni (możesz użyć go jako sub-zapytania, aby zrobić to wszystko w jednym żądaniu).

Wygląda na to, że NSFetchRequest zawiera wszystkie wymagane elementy, ale wydaje się, że nie działa. Oto, co próbowałem zrobić:

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: @"[email protected] > 1"]; 
request.resultType = NSDictionaryResultType; 

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

To nie działa. :-P Jeśli spróbuję, otrzymam komunikat "Nieobsługiwana liczba wyrażeń funkcji: (dayString)" podczas próby pobrania. Nie sądzę, że dayString w "dayString. @ Count" ma nawet znaczenie w powyższym kodzie ... ale wstawiłem to dla jasności (liczba SQL działa na zgrupowanych wierszach).

Moje pytanie brzmi: czy to możliwe, a jeśli tak, to jaka jest składnia, aby to zrobić? Nie mogłem znaleźć niczego w dokumentach CoreData, aby wskazać, jak to zrobić.

Znalazłem jeden podobny wpis SO, którego teraz niestety nie mogę znaleźć, który dotyczył liczenia w klauzuli posiadania (nie sądzę, żeby była grupa). Ale plakat porzucił i zrobił to inaczej, nie znajdując rozwiązania. Mam nadzieję, że jest to bardziej jednoznaczne, więc może ktoś ma odpowiedź. :)

Dla porównania, jest to, co robię teraz, że nie działa, ale wymaga powrocie prawie wszystkie wiersze, ponieważ istnieje bardzo niewiele duplikaty w większości przypadków:

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

Mam następnie do pętli nad wynikiem i zwracają tylko wyniki, które mają dayStringCount> 1. Jaka powinna być klauzula mająca. :-P

UWAGA: Wiem, że CoreData nie jest SQL. :) Chciałbym tylko wiedzieć, czy mogę wykonać równoważny typ operacji z taką samą wydajnością jak SQL.

+0

Zgaduję odpowiedź ma coś wspólnego z użyciem podzapytania w NSPredicate. Czy próbowałeś tego? –

+0

Spójrz na http://www.atomicbird.com/blog/icloud-complications-part-2, wydaje się, że w to włożyłeś trochę uwagi :) –

+0

Nie jestem pewien, jak podzapytanie mi tu pomoże. Chyba żebym mógł wykonać podzapytanie, które zrobiło grupę, z klauzulą ​​mającą w sobie licznik. ;-) Można również wykonać sprzężenie z tą samą tabelą, aby wykryć duplikaty, ale nie jestem też pewny, jak by to zrobić z danymi podstawowymi. Odnośnie tej strony, zapobiegając oszustwom, które znam. Problem polega na oczyszczeniu istniejących duplikatów. Wygląda na to, że jego rozwiązaniem było zrobienie tego, co zrobiłem powyżej (uwzględnij liczbę w wynikach zapytania). To wciąż śmierdzi, ponieważ przyniesie wiele wyników, jeśli stół będzie duży. Dzięki! – stuckj

Odpowiedz

5

Tak jest to możliwe. Jako ścieżki klucza nie można odwoływać się do count, ale można ją określić jako zmienną. Podobnie jak w SQL. W moim przykładzie mam miasta utworzone ze zduplikowanymi nazwami.

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) 

Kompletny plik zabaw w: https://gist.github.com/pronebird/cca9777af004e9c91f9cd36c23cc821c

+0

Rozumiem. Zasadniczo wygląda dokładnie to, co próbowałem zrobić, ale wyrażenie jest wpisane jako obiekt w ciągu formatu, zamiast używać ścieżki klucza. Cóż, w szybkim tempie, ponieważ nie istniało, kiedy to napisałem. :-P Dzięki za plac zabaw ... Nie mam łatwego dostępu do oryginalnego kodu. Wydaje się działać, więc przyjmuję to jako teraz poprawną odpowiedź. Ciekaw jestem, czy Apple właśnie naprawił to zachowanie, skoro to napisałem, czy też miałem niewłaściwą składnię. No cóż. :-P – stuckj

+0

@stuckj Mam również wersję Objective-C, ale o wiele łatwiej jest biegać i testować na placu zabaw Swift. Myślę, że na placu zabaw działa kilka sim z iOS 8, więc ta sztuczka też powinna działać. – Andy

5

Najlepszy mogę wymyślić to:

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

Ale to naprawdę nie jest znacznie różni się od tego, co używasz.

+0

Tak, jeśli nie otrzymam lepszych odpowiedzi, po prostu przyjmuję ten. Filtrowanie jest trochę ładniejsze niż pętla for. – stuckj

+0

Nie jestem pewien, dlaczego havingPredicate jest tak naprawdę tam, ponieważ nie mogę znaleźć żadnych przypadków, w których ma on inny skutek niż tylko używanie predykatu. Nawet nie wspomina się o tym w Internecie inaczej niż w dokumentacji. –

+0

Ok. Przyjmuję twoją odpowiedź. Może ktoś w przyszłości znajdzie jakiś sposób, aby rzeczywiście wykonać bardziej efektywne zapytanie, ale na razie zamierzam stosować to podejście. :-P – stuckj

0

Najlepszy sposób na znalezienie duplikatów w danych podstawowych zależy od danych. Według Efficiently Importing Data i zakładając, że trzeba importować mniej niż 1000 PersistedDays, sugeruję tego rozwiązania:

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 */ 
    } 
} 

Dla prędkości UP dayString indeksu w danych Core.

Można również ograniczyć zbiór danych, jeśli pamiętać, znacznik czasu lub daty ostatniego duplikatu oczyścić:

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

Dzięki, ale wydaje się to mniej wydajne niż użycie grupy, ponieważ można zamiast używać porównania liczb całkowitych dla liczby> 1 zamiast porównania łańcuchów na każdym wpisie. Zdecydowanie będę mieć> 1000 wpisów. – stuckj

Powiązane problemy