Fetching distinct values from cloudkit swift - ios

I have a database students with columns :
courseID, StudentID,FirstName, LastName
Now a student can take more than one course.
I have a screen where I am fetching just the students names.
And so obviously I am getting duplicate values. Now I know I can change the type of courseID from string to a string list to do away this problem but that would mean a hell lot of coding changes. is there anyway I can modify my CKQuery to fetch distinct values?
As of now the code looks like this:
func FetchRecords(){
print("Fetch Records")
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Students", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["FirstName","LastName","StudentID"]
var newStud = [Student]()
print("Begin")
operation.recordFetchedBlock = { (record) in
let stud_rec = Student()
print(record["FirstName"] as! String)
stud_rec.FName = (record["FirstName"] as! String)
stud_rec.LName = (record["LastName"] as! String)
stud_rec.MatrN = (record["StudentID"] as! String)
newStud.append(stud_rec)
}

Amrita I think there is data model design issue where you have felt one of consequences -- a lot of "duplicate values".
The root solution is to avoid many-many relationship between courseID and studentID via a record type say Enrollment to track which student takes which course at which year. This of course demands a "hell lot of coding change." that you also want to avoid.
So maybe you may try this quick surface patch to "fetch distinct values" where you may use Swift Set operations to deal with fetched duplicates as below:
let fetchedDuplicate = ["Jon", "Sid", "Tom", "Jon"]
let uniqueSet = Set(fetchedDuplicate) // it is now {"Jon", "Sid", "Tom"}
let uniqueArray = Array(uniqueSet) // ["Jon", "Sid", "Tom"]
I hope it helps.

Related

Is it possible to perform NSBatchUpdateRequest with an array of NSManagedObjectID?

Currently, I perform multiple update operations via the following code.
func updateOrders(_ updates : [(objectID: NSManagedObjectID, order: Int64)]) {
if updates.isEmpty {
return
}
let coreDataStack = CoreDataStack.INSTANCE
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
for update in updates {
let objectID = update.objectID
let order = update.order
let nsPlainNote = try! backgroundContext.existingObject(with: objectID) as! NSPlainNote
nsPlainNote.order = order
}
RepositoryUtils.saveContextIfPossible(backgroundContext)
}
}
Since I would like to
Make the update operations run faster
Avoid delegate of NSFetchedResultController from being notified
I would like to utilise NSBatchUpdateRequest for performing such update operation.
However, I don't find a way, how I can apply array of NSManagedObjectID and array of Int64 value, to NSBatchUpdateRequest.
Given an array of NSManagedObjectID and Int64, is it possible to use NSBatchUpdateRequest to perform updated on CoreData?
You must use NSPredicate to set object id
func updateOrders(_ updates : [(objectID: NSManagedObjectID, order: Int64)]) {
updates.forEach {
let request = NSBatchUpdateRequest(entityName: "NSPlainNote")
request.propertiesToUpdate = ["order": $0.order]
request.predicate = NSPredicate(format: "objectID == %#", $0.objectID)
let result = try? context.execute(request)
}
}
NSBatchUpdateRequest is not suitable for your task since using it makes sense for large amount of records with a common attribute's value so that you can filter all by your criteria and update all fields with your values at once.
The fact is that the NSBatchDeleteRequest is an NSPersistentStoreRequest which operates at the SQL level in the persistent store itself and it doesn't update your in-memory objects after execution thats why it works so fast and Core Data translates your native requests to a SQL ones where you can not use dynamically code to get and insert needed data from dictionary etc. but you can update the current value of a filed e.g.:
let batchRequest = NSBatchUpdateRequest(entityName: "Note")
batchRequest.predicate = predicate
// Increase `order` value
batchRequest.propertiesToUpdate = ["order" : NSExpression(format: "order + 1")]
do {
try context.execute(batchRequest)
}
catch {
fatalError(error.localizedDescription)
}

How to list a Property in realm Swift 3

How to list a property in realm DB like SELECT columnName FROM mytablein SQL?
Here is my try:let person = self.realm.objects(Person.self).filter("age")
You can access a single property (since Realm models are native objects, they have properties, not columns) of all instances of your particular model class stored in Realm using map.
filter, as its name suggests can be used to only work on a subset of all instances of a certain type that all fulfilled the same condition (for example you can use filter to find all people whose age is above 18 by saying: let adults = self.realm.objects(Person.self).filter("age > 18")).
Get the age property of all instances of Person persisted in Realm using map:
let people = self.realm.objects(Person.self)
let ages = people.map{$0.age}
or in one line giving an Array as an output:
let ages = Array(self.realm.objects(Person.self)).map{$0.age}
you can get list of records like this
let realmCities = try! Realm()
lazy var arrDefaultCities: Results<Cities> = { self.realmCities.objects(Cities.self).sorted(byKeyPath: "cityName", ascending: true) }()
func filterCities()
{
let statePredicate = NSPredicate(format: "stateId = %d", objState.stateId)
arrDefaultCities = try! Realm().objects(Cities.self).filter(statePredicate).sorted(byKeyPath: "cityName", ascending: true)
self.filterArrCities.removeAll()
for objCities : Cities in arrDefaultCities{
if objCities.cityName == APP_DELEGATE.currentCity
{
self.objCity = objCities
}
self.filterArrCities.append(objCities.cityName)
}
}

Adding up an attribute inside entity for CoreData ios 10

I have a tableView and that tableview is being populated with data from Coredata. The data breaks down like this.
Entity - Person
Entity - Statement
The statement entity has an attribute called amountOwed and it is of decimal type.
The relationships is that a person can have many statements, but each statement belongs to a single person.
Here is the path of the data that I would like to add up. Person > Statement > AmountOwed.
In the tableView function I have a let that represents the Person entity.
let person = fetchedResultsController.object(at: indexPath)
I know its working because I can print out the persons name like so
print(person.name) // Bob
What I want to be able to do is add up all the amountOwed attributes for each Person inside the Statement entity and display them on a cell.
I have been trying to follow an example of calculated fetches but I seem to not quiet understand how to target my Statements Entities which are linked to each Person entity.
let fetchRequest = NSFetchRequest<NSDictionary>(entityName:"statement")
fetchRequest.resultType = .dictionaryResultType
let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "sumDeals"
let specialCountExp = NSExpression(forKeyPath: #keyPath(Person.statement[indexPath].amountOwed))
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [specialCountExp])
sumExpressionDesc.expressionResultsType = .interger32AttributeType
fetchRequest.propertiesToFetch = [sumExpressionDesc]
do{
let results = try coreDataStack.managedContext.fetch(fetchRequest)
let resultDict = results.first!
let numDeals = resultDict["sumDeals"]
print(numDeals!)
}
catch let error as NSError{
print("Count not fetched \(error), \(error.userInfo)")
}
Do I need to fetch a Statement entity or should I just use the FetchedREsultsController? If I do use my fetchedResultsController does the keypath to the Statement Entity look like this
person[indexPath].statement.amountOwed
You can do that in one line. If the relationship from Person to Statement is called statements, you get the total of the amounts with
let amountTotal = newPerson.value(forKeyPath: "statements.#sum.amount") as? Int64
Change the downcast at the end from Int64 to whatever is appropriate for your amount attribute-- Double or whatever.
OK on your People+CoreDataClass add:
var totalOwed: Float {
get {
var value: Float = 0
if let statements = self.statements.allObjects() as? [Statement] {
for s in statements {
value = value + s.sum
}
}
return value
}
}
And remove all the code from your fetch that is unnecessary

What's the simplest yet efficient way to check for duplicates in a realm Results<>?

I'm trying to list contacts that a user can add to an event, but I want to filter the results so duplicates don't show up. So if I added John Doe his contact won't show up in the list of contacts. I'm not not well versed with NSPredicate so I'm not sure if that's the best way or to convert the Results array to something easier to work with.
Here is example on kotlin, but very close to swift.
You can do something like that. Instead of getting RealmResults you can get List of users that filtered by name.
fun filteredUsers(){
val realm = Realm.getDefaultInstance()
realm.where(UserRealm::class.java)
.findAllAsync()
.asObservable()
.filter { users -> users.isLoaded }
.flatMap { users -> Observable.from(users) }
.filter { user -> !user.name.equals("John Doe") }
.observeOn(AndroidSchedulers.mainThread())
.doOnError { err -> err.printStackTrace() }
.toList()
.subscribe { userList -> print(userList) }
}
And also I found in docs much easier way for swift:
// Query using a predicate string
var tanDogs = realm.objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'")
// Query using an NSPredicate
let predicate = NSPredicate(format: "color = %# AND name BEGINSWITH %#", "tan", "B")
tanDogs = realm.objects(Dog).filter(predicate)
You want a distinct query, but Realm doesn't support them natively yet.
You can however, get the data out of a Realm and do the deduplication yourself, but then you lose Realm's auto-updating Results type:
let realm = try! Realm()
let currentUser = realm.objects(User).filter("me == true").first!
let uniqueContactNames = Set(currentUser.contacts.valueForKey("name") as! [String])
See #1103 for more details and suggested workarounds.

Can't load Core Data in to-many attribute

I have been unable to add and read core data attributes in a one-to-many
relationship. After reading many SO, Apple Docs and other articles I still have
not accomplished this. To make a simple test, I created a standard Master Detail
App with Core Data. This works with no issues for the main entity.
The core data relationship is as shown below.
Here is the code to place some test data into the store. Note that I am
attempting to add the to-many data as a Set for each InvItem and have made the
keywordList attribute Transformable. I thought this was the best approach, but
obviously that is not working here.
func seedInvItems(num : Int) {
for index in 1...num {
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName("InvItem", inManagedObjectContext: kAppDelegate.managedObjectContext) as! InvItem
newManagedObject.name = "myName\(index)"
newManagedObject.category1 = "myCategory1x\(index)"
newManagedObject.compartment = "myCompartment\(index)"
newManagedObject.entryDate = NSDate()
//and for the one-to-many relationship
var myStoreKeywordSet = Set<String>()
myStoreKeywordSet = ["one","two","three"]
// do an insert just for grins
myStoreKeywordSet.insert("four")
let newManagedObject2 = NSEntityDescription.insertNewObjectForEntityForName("InvItemKeyword", inManagedObjectContext: kAppDelegate.managedObjectContext) as! InvItemKeyword
newManagedObject2.keywordList = myStoreKeywordSet
}//for in
kAppDelegate.saveContext()
let fetchRequestTest = NSFetchRequest(entityName: "InvItem")
let sorter : NSSortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequestTest.sortDescriptors = [sorter]
var resultsTest : [InvItem]?
do {
resultsTest = try kAppDelegate.managedObjectContext.executeFetchRequest(fetchRequestTest) as? [InvItem]
for object in resultsTest! {
let myRetrieveKeywordSet = object.invitemkeywords
print(myRetrieveKeywordSet)
}//for in
} catch let error as NSError {
//what happens on failure
print("And the executeFetchRequest error is \(error.localizedDescription)")
}//do catch
}//seedInvItems
For completeness, I did create the NSManagedObject subclasses for the Core
Data Entities.
When running the app, the master detail for InvItem behaves as expected but I
get no storage of the to-many items.
Here is the console log (for one InvItem):
Optional(Relationship 'invitemkeywords' fault on managed object (0x7f985a62bb70) (entity: InvItem; id: 0xd000000000700000 ; data: {
category1 = myCategory1x3;
compartment = myCompartment3;
entryDate = "2016-02-09 02:10:21 +0000";
invitemkeywords = "";
name = myName3;
}))
Looking at the database, there is no data for the keywordList.
Any help would be appreciated. Xcode 7.2.1 IOS 9.2
You need to set the relationship from newManagedObject to newManagedObject2. In fact, because the relationship is one-many, it is easier to set the to-one relationship:
newManagedObject2.invitem = newManagedObject
and let CoreData handle the inverse, to-many relationship.
As to the keyWordList not being populated, I wonder whether your SQL browser is unable to decode CoreData's NSSet? I also wonder whether you need to have keywordList as an NSSet? The relationship from InvItem is already to-many, so your model implies that each InvItem can have many InvItemKeywords, each of which holds many keywords in its keywordList attribute: is that what you intended?

Resources