Fetch Specific Number of Random Rows from CoreData - iphone

I'm using the code below to Fetch a queried set of all rows using CoreData matching the search criteria: itemType = 1.
But what I need to do is to Fetch a specific number of Random rows from the data instead.
For example, instead of retrieving all 100 rows of data in which the column name dataType = 1, I need to get 25 rows randomly in which dataType = 1.
I'm hoping there is relatively painless solution.
Any help is appreciated.
lq
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"MyAppName"
inManagedObjectContext:[self managedObjectContext]]];
NSError *error = nil;
NSPredicate *predicate;
NSArray *fetchResults;
predicate = [NSPredicate predicateWithFormat:#"(itemType = %i)", 1];
[request setPredicate:predicate];
fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
if (!fetchResults) {
// NSLog(#"no fetch results error %#", error);
}
self.mutableArrayName = [NSMutableArray arrayWithArray:fetchResults];
[request release];

You can not actually fetch random rows. A reasonable randomization strategy may be to fetch all of the objects matching your predicate, and then randomly select a specific number of objects.
Anyway you can use the following methods of NSFetchRequest:
- (void)setFetchLimit:(NSUInteger)limit
- (void)setFetchOffset:(NSUInteger)limit
Basically, setFetchLimit allows you to define how many rows you want to fetch (in your case you will set limit to 25), while setFetchOffset defines the offset at which rows will begin being returned (see the documentation of the fetchOffset property for details).
This is not a random process, but you may randomly generate the offset. However, it is worth noting here that, depending on the offset, you may then fetch a number of objects falling between zero and your fetch limit.

You could also use reference approach. When you sort by view counts.
I posted long time ago about it: http://www.alterplay.com/ios-dev-tips/2010/06/fetch-random-record-with-coredata.html
Sorry for formatting. it's broken after switching from Blogger to Wordpress.

Related

How to make NSExpression's expressionForFunction:withArguments: honour the fetch request's predicate

I'm using Core Data to store simple entities that consist of a value and a timestamp. I'm looking to make a fetch request which returns the latest value added, as well as a running average of all values. This all seemed straightforward enough until I attempted to use an NSPredicate to filter the results to within a certain time period.
I'm using NSExpression's expressionForFunction:withArguments: method to determine the average. By setting the launch flag -com.apple.CoreData.SQLDebug 1, I can clearly see that only the latest value is adhering to my date predicate. The average calculating is instead being performed as a subquery, but not taking my date predicate into account:
SELECT (SELECT avg(t1.ZVALUE) FROM ZEVENT t1 WHERE t1.Z_ENT = ?), t0.ZVALUE FROM ZEVENT t0 WHERE ( t0.ZTIMESTAMP >= ? AND t0.Z_ENT = ?) ORDER BY t0.ZTIMESTAMP DESC
What's the most efficient (and scalable) way of determining the average value while still honouring my NSFetchRequest's predicate?
For reference, here is my code in it's entirety:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Reading" inManagedObjectContext:moc];
[request setEntity:entity];
[request setSortDescriptors:#[sortDescriptor]];
[request setResultType:NSDictionaryResultType];
// Filter by date where necessary
NSPredicate *datePredicate = [NSPredicate predicateWithFormat:#"(timestamp >= %#)", toDate];
[request setPredicate:datePredicate];
NSExpression *valueExpression = [NSExpression expressionForKeyPath:valueKey];
NSExpressionDescription *valueDescription = [[NSExpressionDescription alloc] init];
[valueDescription setName:#"value"];
[valueDescription setExpression:valueExpression];
[valueDescription setExpressionResultType:NSDoubleAttributeType];
NSExpression *avgExpression = [NSExpression expressionForFunction:#"average:" arguments:[NSArray arrayWithObject:valueExpression]];
NSExpressionDescription *avgDescription = [[NSExpressionDescription alloc] init];
[avgDescription setName:#"averageReading"];
[avgDescription setExpression:avgExpression];
[avgDescription setExpressionResultType:NSDoubleAttributeType];
[request setPropertiesToFetch:#[avgDescription, valueDescription]];
I see two errors. There's no initialization shown for toDate. I also notice that you are passing setPropertiesToFetch: an array of NSExpressions, but the documentation calls for an array of NSPropertyDescriptions. I would expect that discrepancy to cause a null result, and populated NSError, when you execute the fetch request.
What result do you see from executeFetchRequest:error:? Be sure to check the NSError result. Idiom is something like this:
NSError *error;
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
if (!results) {
NSLog(#"%# fetch error for request %#: %# %#", fetchRequest,
error.localizedDescription, error.localizedFailureReason);
}
I would take a different approach. One fetch request has a limit of 1, sort timestamp descending, and returns the latest timestamp. Add a predicate if you like, for time restriction. Then use a second fetch request to compute the average of the timestamps. You could even encapsulate these calls into their own methods:
-(NSDate *)latestTimestamp:(NSManagedObjectContext *)moc;
-(NSNumber *)averageValueSinceTime:(NSDate *)intervalStart
context:(NSManagedObjectContext *)moc;

setFetchBatchSize on a NSFetchRequest on the iPhone

I don't understand the meaning of setFetchBatchSize.
In the following code, I set the fetch batch size to 20, but I still get 49 objects in my results array (which are ALL the objects from the query)
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"company.id = %#", company.id];
NSFetchRequest *req = [CategoryEntity requestAllSortedBy:#"id" ascending:YES withPredicate:predicate];
[req setFetchBatchSize:20];
NSArray *results = [CategoryEntity executeFetchRequest:req];
So what's the meaning of setFetchBatchSize, since it doesn't seem to limit the request?
To limit the size use fetchLimit.
With batch size, the objects are faulted. They look like they are there but those are just stubs. When you access them - behind the scenes - the values are then populated.

iphone coredata fetch-request sorting

I'm using core data and fetching the results successfully. I've few questions regarding core data
1. When I add a record, will it be added at the end or at the start of entity.
2. I'm using following code to fetch the data. Array is being populated with all the records. But they are not in the same order as I entered records into entity. why? on what basis default sorting is used?
NSFetchRequest* allLatest = [[NSFetchRequest alloc] init];
[allLatest setEntity:[NSEntityDescription entityForName:#"Latest" inManagedObjectContext:[self managedObjectContext]]];
NSError* error = nil;
NSArray* records = [managedObjectContext executeFetchRequest:allLatest error:&error];
[allLatest release];
3. The way that I enter the records, 1,2,3,4......... after some time, I want to delete the records that I entered first(i mean oldest data). Something like delete oldest two records. How to do it?
A sort on the array you have looks like this:
NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:#"SortKey" ascending:YES] autorelease];
NSArray *sortedList = [records sortedArrayUsingDescriptors:[NSArray arrayWithObjects:descriptor, nil]];
The initWithKey: argument is the property you would like to sort on. If you have multiple keys to sort on, they can be added to the sortedArrayUsingDescriptors: argument as a list. The list will be sorted by the first descriptor, then each group by the next descriptors.
-dan
Ordering is not guaranteed in any way. If you care about order, you need to include an attribute in your entity (for example, call it "sequenceNumber") and use that in a sort descriptor with your NSFetchRequest.

iPhone OS: Fetching a random entity instance using NSPredicate Nsfetchrequest and core data

Working on an app where I have a large collections of managed objects against which I want to fetch a few random instances.
My question is, is there any way I can use NSPredicate and NSFetchRequest to return several objects at random.
I saw that you could actually add a NSFetchRequest into the entity using the data modeler, any way to do the random fetch using this?
Also what would be the best method for determining the "count" of a table so I can set the bounds of the random number generator.
let me know if you need more details.
Thanks!
Nick
Instead use the fetchLimit in conjunction with the fetchOffset that way you can efficiently fetch only a single entity into memory:
NSFetchRequest *myRequest = [[NSFetchRequest alloc] init];
[myRequest setEntity: [NSEntityDescription entityForName:myEntityName inManagedObjectContext:myManagedObjectContext]];
NSError *error = nil;
NSUInteger myEntityCount = [myManagedObjectContext countForFetchRequest:myRequest error:&error];
NSUInteger offset = myEntityCount - (arc4random() % myEntityCount);
[myRequest setFetchOffset:offset];
[myRequest setFetchLimit:1];
NSArray* objects = [myManagedObjectContext executeFetchRequest:myRequest error:&error];
id randomObject = [objects objectAtIndex:0];
This may not be exactly how you implement this, but hopefully it will get you started.
Somewhere in your header or at the top of your implementation file:
#import <stdlib.h>
#import <time.h>
Elsewhere in your implementation:
//
// get count of entities
//
NSFetchRequest *myRequest = [[NSFetchRequest alloc] init];
[myRequest setEntity: [NSEntityDescription entityForName:myEntityName inManagedObjectContext:myManagedObjectContext]];
NSError *error = nil;
NSUInteger myEntityCount = [myManagedObjectContext countForFetchRequest:myRequest error:&error];
[myRequest release];
//
// add another fetch request that fetches all entities for myEntityName -- you fill in the details
// if you don't trigger faults or access properties this should not be too expensive
//
NSArray *myEntities = [...];
//
// sample with replacement, i.e. you may get duplicates
//
srandom(time(NULL)); // seed random number generator, so that you get a reasonably different series of random integers on each execution
NSUInteger numberOfRandomSamples = ...;
NSMutableSet *sampledEntities = [NSMutableSet setWithCapacity:numberOfRandomSamples];
for (NSInteger sampleIndex = 0; sampleIndex < numberOfRandomSamples; sampleIndex++) {
int randomEntityIndex = random() % myEntityCount; // generates random integer between 0 and myEntityCount-1
[sampledEntities addObject:[myEntities objectAtIndex:randomEntityIndex]];
}
// do stuff with sampledEntities set
If you need to sample without replacement, to eliminate duplicates, you might create an NSSet of randomEntityIndex NSNumber objects, instead of just sampling random ints.
In this case, sample from an ordered NSSet, remove NSNumber objects as you pull them out of the bag, and decrement myEntityCount for the purposes of picking a random NSNumber object from the set.
I've searched around a lot for this, essentially Coredata won't give you any random rows, and it is not meant to. You have to create your own.
This is what I came up with up with, assuming we are using an NSPredicate and theres no primary Unique key, this is the best possible answer i think with the least overhead.
Set NSFetchRequest type to NSManagedObjectID. Turn everything off, to minimize overhead.
Execute fetch request with your desired predicate, Do Not use any FetchLimit.
From the received NSManagedObjectID array. get your random number of objects. this is a good solution: Get n random objects (for example 4) from nsarray
Now you've got random NSManagedObjectIDs of your desired count, (which are more or less random)
Loop through the random objectID array and Use NSManagedObjectContext objectWithID: to get the objects.
If you are fetching all the objects anyway, there is no need for the first request to get the object count. You can just use something like:
myEntityCount = [myEntities count]
Solution suggested by Core will not work if you need to randomize the fetch within a subset table rows restricted by a predicate (e.g. "where something < something").
The best solution I have so far (where I do not need to fetching all or large number of rows) is using randomly selecting based on a primary key (off course the requires a primary key in the table, preferably w/o any missing values).
This is what I did in Swift based on Corey Floyd's answer, given a Verb entity.
A few manual tests give me satisfactory random results.
let request = Verb.fetchRequest()
do {
let count = try self.context.count(for: request)
let randomOffset = Int.random(in: 0..<count)
request.fetchOffset = randomOffset
request.fetchLimit = 1
let verbs = try self.context.fetch(request)
}
catch let error {
print(error.localizedDescription)
}

Core Data : How to check for the presence of Many to Many relationship

I have a "Song" Entity and a "Tag" entity and they have a many to many relationship between them. A Song can have multiple Tags and a Tag can be applied to multiple Songs.
I want to check if a Song has a particular Tag associated with it. If the Song has the Tag associted with it, I want to show a checkmark in the table view.
For a similar logic, in Apple "TaggedLocations" sample code, the following check is made to check for the presence of the relationship.
if ([event.tags containsObject:tag]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
This may be inefficient if there are a lot of Tags in the database as this will fetch all of them in the memory. Please correct me if I am wrong here.
Is there a more efficient way to check if the Song is associated with a particular Tag instead of checking in Song.Tags?
It's actually pretty easy to do, if completely undocumented. You want to create a fetch request with a predicate that has a set operation. If we imagine that your Tag model has a property called tagValue, the predicate you care about is "ANY tags.tagValue == 'footag'"
NSString *tagSearch = #"footag";
// However you get your NSManagedObjectContext. If you use template code, it's from
// the UIApplicationDelegate
NSManagedObjectContext *context = [delegate managedObjectContext];
// Is there no shortcut for this? Maybe not, seems to be per context...
NSEntityDescription *songEntity = [NSEntityDescription entityForName:#"Song" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:songEntity];
// The request looks for this a group with the supplied name
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.tagValue == %#", tagSearch];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
[request release];
You are correct, using that code will retrieve the entire set and the object comparison may be quite complex, depending on how many properties and relationship are part of the object's entity.
Anyway, you can not avoid a set comparison for inclusion. Probably, the best you can do is to avoid fetching all of the properties/relationships by asking Core Data to retrieve NSManagedObjectID Objects only.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Tag" inManagedObjectContext:[self managedObjectContext]]];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSManagedObjectID objects are guaranteed to be unique, therefore you can safely use them to check for set inclusion. This should be much more efficient for a performance perspective.