开发者

Problem with SUBQUERY to find max (last?) Date

开发者 https://www.devze.com 2023-03-16 03:32 出处:网络
I have two entities \'Times\' <*-> \'FileList\'. \'FileList\' - has relation \'whenDownload\', reverse relation from \'Times\' is \'wichFile\'.

I have two entities 'Times' <*-> 'FileList'. 'FileList' - has relation 'whenDownload', reverse relation from 'Times' is 'wichFile'. Every file in 'FileList' could be download several times and myApp stores several versions of files in file system. 'Times' helps me to find exact version and other information about file (comments, etc).

So I have filesArray with several objects from 'FileList' and try to find last download version each FileList objects from array to understand is it necessary to download new versions or not. Server-side with files is not mine.

My code:

NSFetchRequest *cutRequest = [[NSFetchRequest alloc] init];
cutRequest.entity = [NSEntityDescription entityForName:@"Times" inManagedObjectContext:localContext];
(NSArray *) listFilesToDownload = [self getListFilesToDownload]; // array with 30-90 files from 10k
NSPredicate * filePredicate = [NSPredicate predicateWithFormat:@"whichFile IN %@", listFilesToDownload];

// doesn't work:
//NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "SUBQUERY(Times, $s, max($s.timeDownload))"]; 
//NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "max (Times, $s, $s.timeDownload)"];
//NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "(SUBQUERY (Times, $s, $s.timeDownload).max != 0)"];
//NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "max (timeDownload)"];
//NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "max (Times.timeDownload)"];
//NSString  *name1 = @"timeDownload";
//NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "max (%K)", name1];
NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "SUBQUERY (Times, $time, max($time.timeDownload)).@count > 0"];

NSArray *allPredicates  =   [NSArray arrayWithObjects: filePredicate, timePredicate, nil];
cutRequest.predicate    =   [NSCompoundPredicate andPredicateWithSubpredicates:allPredicates];
cutRequest.fetchBatchSize = 300;
NSArray     *arrayRequest   开发者_如何学编程= [localContext executeFetchRequest:cutRequest error:&error];
[cutRequest release];

also I tried single predicate:

 NSPredicate * timePredicate =  [NSPredicate predicateWithFormat : @ "(whichFile IN %@) AND (SUBQUERY (Times, $s, max($s.timeDownload))"];

but still have "Unable to parse the format string..."

I'm not familiar with Core Data. Some posts (one, two and three) were interesting, but not enough for me. I couldn't find enough details about SUBQUERY syntax with functions.

1. Could you explane how to get correct predicate with subquery in my case ?

2. What are you recommend to read about subquery (except Predicate programming guide :)

Hope, the question would be interesting for everybody.

Looking forward for your advices.

Nik


There are a couple things wrong with your predicate format string:

SUBQUERY (Times, $s, max($s.timeDownload))
  1. SUBQUERY returns a collection (ie, an array). Predicates want expressions that evaluate to YES or NO, not to an array. An array is not a boolean value. Typically you see something more along the lines of SUBQUERY(...).@count > 0, meaning "perform this subquery, and then return YES if it returned any objects". This may be appropriate in your case, though I'm not really sure; I'm just evaluating the syntactic correctness of your format string.

  2. The third parameter to a SUBQUERY must itself be a predicate expression. It must evaluate to YES or NO. max(oneThing) does not evaluate to YES or NO; it evaluates to oneThing.

Assuming you get all of this correct, I'm not sure it's even going to be recognizable by Core Data as the predicate to a fetch request. Core Data has some pretty stringent requirements on what sorts of things you can do in a predicate. Additionally, I'm pretty sure that SUBQUERY qualifies as an "aggregate expression", which Core Data does not support.

I'd say that about 99% of the time you think you need to use a SUBQUERY, you don't, and you actually shouldn't. It's very rarely useful, and its use is bizarre enough that it generally makes your predicate harder to understand, and thus less maintainable in the future.

In a nutshell: find a different way to do this. You have a list of files, and you want to get the Times object corresponding to the last time it was downloaded, right? I think that'd be something like this:

NSPredicate *p = [NSPredicate predicateWithFormat:@"whichFile.name IN %@ AND timeDownload = whichFile.whenDownload.@max.timeDownload", listFilesToDownload];

This assumes that listFilesToDownload is an array of file names, and that your FileList entity has a name property that's the name of the file and matches one in the list...

The bit that (I think) you're interested in is this:

timeDownload = whichFile.whenDownload.@max.timeDownload

This will evaluate to YES if the download time of this file is equal to the maximum download time of all of the Times of this Time's FileList. Or something like that. Your naming is pretty terrible (excusable, since it doesn't appear that English is your native language), and that's definitely hindering my explanation, but there you go.

Enjoy.


As a rule of thumb, when you find yourself having to use a subquery in a predicate that is often a sign that you've got a design problem. It usually means that (1) your fetching against the wrong entity or (2) that you data model is suboptimal.

In this case, I think it is (2).

Let's make the following assumptions about your requirements.

  • You are downloading multiple versions of same filename from a server.
  • Each file is stored external to Core Data and that you only need to put the filename, path to the file on disk and the download time in Core Data itself.

You can then simplify your data model to just:

DownFile{
  name:string
  path:string
  downloadTime:date
}

Each DownFile instance stores the information for a single downloaded file. You may have many files with the same name (or id number or other identifier) but each version of the filename will have a unique path on disk and a unique download timestamp.

To find the last download version of a file with a particular name, you would use a predicate for just the name.

NSPredicate *p=[NSPredicate predicateWithFormat:@"name == %@", soughtName];

Then you can provide a sort descriptor which sorts by date putting the latest date at the top:

NSSortDescriptor *sort=[NSSortDescriptor sortDescriptorWithKey:@"downloadTime" ascending:NO];

When you run a fetch with the above predicate and sort, it will return an array of all instances of DownFile that have the same name as the soughtName variable all sorted by descending date. To find the last downloaded version just get the zero element of the array.

Besides being more simple, this design is way faster than running subqueries against a vast number of objects.

Even if this particular model won't work for you, it should provide you a starting point. Core Data is intended to model/simulate real-world objects, conditions or events and the relationships between them. Therefore your model should reflect what is really going one. In this case, you have

downloaded files -->> Object
download time -->> Event

Since each file is unique only by its download time, you need to make that the major attribute of the entity that models the files. (Note that an entity models a single instances of a real-world object, condition or event.) The DownFile entity lets you model/simulate one real-world file by name location and the critical event of its time of download.

Because the entity closely models reality, that makes fetching the actual entity instances much, much easier.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号