2017-12-11 15 views
1

私は定期的にサーバからデータをダウンロードするアプリを開発しています。データを更新する必要がある場合は、次のようなものを使用してレコードを更新するか、存在しない場合は新しいレコードを挿入します。エンティティは、すでに各ループに存在するかどうかの確認コアデータを使用してレコードを挿入/更新する最も効率的な方法は?

let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Trip") 
    for csvTrip in csvTrips { 
     var trip: NSManagedObject! 

     let tripId = Int(csvTrip[0])! 
     fetchRequest.predicate = NSPredicate(format: "id = %d", tripId) 

     if (context.count(for: fetch) == 0) { 
      trip = NSEntityDescription.insertNewObject(forEntityName: "Trip", into: context) 
      trip.setValue(tripId, forKey: "id") 
     } else { 
      tripObject = (context.fetch(fetch) as! [NSManagedObject])[0] 
     } 

     // Set other properties 
    } 

はちょうど千以上のいくつかの企業との大きな問題となっており、確認せずにそれらを挿入するよりも、それは約100倍遅くなります。私は最初にすべてのエンティティをフェッチしようとしましたが、まだそれぞれをループしてidを配列などに追加する必要がありますが、あまり高速ではありません。私はコアデータがMySQLと同じではないことを知っていますが、INSERT ...と同様の機能はないと信じるのは難しいです.DUPLICATE KEY UPDATEは非常に高速です。何か不足していますか?

+0

すべてのIDを取得し、それらを 'Set'にロードするのはかなり速いはずです – Paulw11

+0

IDを取得する方法はありますか?私が知っている唯一の方法は、すべてのオブジェクトをフェッチし、それらをループしてセットに追加することです。 –

+0

fetch要求の 'propertiesToFetch'プロパティを設定すると、' id'だけを返すことができます。また、結果の型を 'dictionaryResultType'に設定します。現在のオブジェクトをすべて取得し、 'map'操作を使ってIDを素早くセットにロードすることができます – Paulw11

答えて

3

数千のエンティティを取得して、SetにIDをロードすると特に長い時間がかかりました。指定されたidが新しいかではなく、必要に応じて新しい旅行を挿入する場合は今、あなたは簡単に確認することができ

let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Trip") 
fetchRequest.resultType = .dictionaryResultType 
fetchRequest.propertiesToFetch = ["id"] 
do { 
    if let results = try self.moc.fetch(fetchRequest) as? [[String:Any]] { 
     let idSet = Set<Int32>(results.flatMap({ (dict) -> Int32? in 
       return dict["id"] as? Int32 
     })) 
    } 
} catch { 
    print("Error reading trips") 
} 

:私のテストで

for csvTrip in csvTrips { 
    if let tripId = Int(csvTrip[0]) { 
     if !idSet.contains(tripId) { 
      trip = NSEntityDescription.insertNewObject(forEntityName: "Trip", into: context) 
      trip.setValue(tripId, forKey: "id") 
     } 
    } 
} 

、これを

次のようなものを使用することができます旅行IDがセットに含まれているかどうかを確認しながら、1.35秒で320,000トリップIDをセットにロードし、0.08秒で10,000の新しいトリップを作成しました。

1

挿入/更新を高速化する方法の1つは、入力配列を適度に小さな「バケット」にスライスし、NSPredicateのIN演算子を使用することです。 IN演算子を使用すると、のすべてのバケットの要素がすでにの単一のクエリを使用してdbに存在するかどうかを確認できます。私はそれをいくつかのコードで説明しましょう。

let bucketSize = 10 

let bucketStart = 0 
let bucketEnd = bucketSize 

while bucketStart < csvTrips.count { 
    let tripBucket = csvTrips[bucketStart..<bucketEnd] 

    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Trip") 
    fetchRequest.predicate = NSPredicate(format: "id in %@", tripBucket.map {Int($0[0])}) 

    // count == bucketSize would imply that all elements in the bucket are also in the db, in which case we simply move on to the next bucket 
    if context.count(for: fetch) != bucketSize { 
     // some of the elements in the bucket are not in the db, 
     // now use your existing code to update the missing ones 
     for csvTrip in tripBucket { 
      // ... 
     } 
    } 

    // update bucketStart and bucketEnd here 
} 

バケットサイズを変更することで、このアルゴリズムの効率を調整できます。入力データの新しいレコードの確率を考慮してサイズを選択する必要があります。バケットのはありません次のコードブロックを入力します。

if context.count(for: fetch) != bucketSize {...} 

バケットサイズが大きすぎると、ほとんどすべてのバケットにデータベースから少なくとも1つの要素が欠落していることを意味します。これはあなたの既存の方法よりも少しでも有利になることを意味します。一方、バケットサイズが小さすぎると、追加のフェッチ要求(id in %@)のオーバーヘッドが大きすぎることになります。