Query SwiftData model for max value of an attribute

I needed help creating the predicate in order to query my swift data model for the record with the largest integer. Is this possible, and is it any more efficient than just fetching all the data and then doing the filtering?

Answered by DelawareMathGuy in 773774022

hi,

i don't think that SwiftData will do this automatically for you (there are aggregate functions available in Core Data for this, i think, but they are not there yet in SwiftData -- if they are, i'd like to know about that).

in the meantime, yes, just go ahead and fetch all records, but limit what's fetched to only the integer property you're interested in. for example, i have an app concerning a model type Location and a model property position: Int and i want to find the maximum position value among all Locations.

private func lastLocationPosition() -> Int? {
	var fetchDescriptor = FetchDescriptor<Location>()
	fetchDescriptor.propertiesToFetch = [\.position]  // only the positions values come back fully realized
	do {
		let locations = try fetch<Location>(fetchDescriptor)
		return locations.map({ $0.position }).max()
	} catch let error {
		print("*** cannot fetch locations: \(error.localizedDescription)")
		return nil
	}
}

it would be easy to slightly modify this function to return a Location? that realizes this maximum value -- compute the maximum and just find the first object among the fetched locations where the maximum is achieved, returning it instead of an Int?). (all remaining properties of the Location returned will be faulted, so you can use the model object with no problem: any property values you want will be faulted in when needed.)

hope that helps,

DMG

Accepted Answer

hi,

i don't think that SwiftData will do this automatically for you (there are aggregate functions available in Core Data for this, i think, but they are not there yet in SwiftData -- if they are, i'd like to know about that).

in the meantime, yes, just go ahead and fetch all records, but limit what's fetched to only the integer property you're interested in. for example, i have an app concerning a model type Location and a model property position: Int and i want to find the maximum position value among all Locations.

private func lastLocationPosition() -> Int? {
	var fetchDescriptor = FetchDescriptor<Location>()
	fetchDescriptor.propertiesToFetch = [\.position]  // only the positions values come back fully realized
	do {
		let locations = try fetch<Location>(fetchDescriptor)
		return locations.map({ $0.position }).max()
	} catch let error {
		print("*** cannot fetch locations: \(error.localizedDescription)")
		return nil
	}
}

it would be easy to slightly modify this function to return a Location? that realizes this maximum value -- compute the maximum and just find the first object among the fetched locations where the maximum is achieved, returning it instead of an Int?). (all remaining properties of the Location returned will be faulted, so you can use the model object with no problem: any property values you want will be faulted in when needed.)

hope that helps,

DMG

You can provide a SortDescriptor and a fetchLimit to achieve this.

If we take the example of @DelawareMathGuy with a Location object that have a position property, you could do something like that

var fetchDescriptor = FetchDescriptor<Location>(sortBy: [
    SortDescriptor(\.position, order: .reverse) // sort the object by descending position
])
fetchDescriptor.fetchLimit = 1 // retrieve only one object

let locations = try? modelContext.fetch(fetchDescriptor)
let maxPositionLocation = locations?.first

As stated by @DelawareMathGuy, derived attributes are not yet supported by SwiftData. You could create your own by defining computed properties or by using the @Transient attributes but this will not be as efficient (I advise reading Paul Hudson from 'hackingwithswift' article about this)

Set fetch limit to one

lazy var maxId = {
    var d = FetchDescriptor<MyThing>(sortBy: [SortDescriptor(\.id, order: .reverse)])
    d.fetchLimit = 1
    return d
}()

then the line of code is

highestId = (try? context.fetch(maxId))?.first?.id ?? -1
Query SwiftData model for max value of an attribute
 
 
Q