0

--- Summary of the Question ---

I have an Entity in Core Data containing the Properties:

name: String
owned: Boolean
price: UInt16

How do I do a Fetch Request in one go (without post processing), to obtain a result collection (Dictionary, as Core Data returns with Aggregate functions), which meets the following description (example of one item in the returned array dictionary):

{
  name = <entity.name>;
  count = <how many identical entity.name I have>
  ownednb = <how many of the identical entity.name have the entity.owned property set>
  expensivenb = <how many of the identical entity.name have price > 20 >
}

From above I solved almost everything, except the "expensivenb" part, see below:

--- What I did, how I did, and what is already working ---

I want to implement the equivalent of the following SQL SELECT with Core Data Fetch Request (in Swift preferably). Most of the parts of it I have solved, only the part below the code I am still missing, and have no idea how to combine NSPredicates / NSExpressions to achieve that, or if it is even possible.

I have an Entity in Core Data containing the Properties (as shown in the "play" SQL):

name: String
owned: Boolean
price: UInt16

Intention is to aggregate the Fetch Request to Core Data, based on certain Rules on the Properties.

I did a sample of an SQL script to see if this I can do this with normal queries in SQL, and to visualize the Result (running and working, the example can be executed in https://www.programiz.com/sql/online-compiler/ or any other online SQL simulator, to see the intended Result):

-- create a table -- this part is handled by the Core Data Entity
CREATE TABLE cards (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
owned bool NOT NULL,
price INTEGER NOT NULL
);
-- insert some values -- this part is handled by the UI (User Input)
INSERT INTO cards VALUES (1, 'Liliana', 1, 24);
INSERT INTO cards VALUES (2, 'Jace', 0, 23);
INSERT INTO cards VALUES (3, 'Liliana', 0, 18);
INSERT INTO cards VALUES (4, 'Jace', 1, 16);
INSERT INTO cards VALUES (5, 'Liliana', 0, 25);
INSERT INTO cards VALUES (6, 'Liliana', 1, 29);
-- fetch some values - this is the Fetch Request to the Core Data
SELECT name, COUNT(name) as totalnb, SUM(owned > 0) as ownednb, SUM(CASE WHEN price > 20 THEN 1 ELSE 0 END) as expensivenb FROM cards GROUP BY name

The problematic part is: SUM(CASE WHEN price > 20 THEN 1 ELSE 0 END) to translate to the Fetch Request as an Aggregate Function with a Condition inside. I do not want to add the Condition as NSPredicate (WHERE) of the Fetch Request - as that would limit the Results; I just want to aggregate by the "transformed" Property.

The code in Swift for the Fetch Request (SELECT of the SQL Query) I also have working (except the missing part):

The Fetch Request:

let request = NSFetchRequest<NSFetchRequestResult>(entityName: "CardData")

Aggregation for COUNT(name) as totalnb:

let keyPathCardName = NSExpression(forKeyPath: "name")
let cardCountExpression = NSExpression(forFunction: "count:", arguments: [keyPathCardName])
let cardCountDescriptor = NSExpressionDescription()
cardCountDescriptor.expression = cardCountExpression
cardCountDescriptor.name = "totalnb"
cardCountDescriptor.expressionResultType = .integer64AttributeType

Aggregation for SUM(owned > 0) as ownednb:

let keyPathOwned = NSExpression(forKeyPath: "owned")
let cardOwnedCountExpression = NSExpression(forFunction: "sum:", arguments: [keyPathOwned])
let cardOwnedCountDescriptor = NSExpressionDescription()
cardOwnedCountDescriptor.expression = cardOwnedCountExpression
cardOwnedCountDescriptor.name = "ownednb"
cardOwnedCountDescriptor.expressionResultType = .integer64AttributeType

Aggregation for SUM(CASE WHEN price > 20 THEN 1 ELSE 0 END) as expensivenb:

// >> This is the part I am missing, but I expect it should be something like the chunk above for the SUM(owned > 0) as ownednb, but with a Condition somehow added inside. <<

And the remaining part of the Fetch Request:

request.propertiesToFetch = ["name", cardCountDescriptor, cardOwnedCountDescriptor]
request.propertiesToGroupBy = ["name"]
request.resultType = .dictionaryResultType

do {
    let result = try context.fetch(request)

    print("\(result)")
} catch {
    print("Error: Failed fetching all Card Data from Context \(error)")
}

Any help would be appreciated.

Thanks, Andrei

Twelveg
  • 3
  • 3
  • 1
    Core data isn't a database. You will probably need to perform this sort of computation after you have fetched the results or consider whether SQLite is a better choice. – Paulw11 Oct 20 '22 at 21:27
  • It is clear to me that Core Data is no database, but since i see everything else is achievable in a similar way - my assumption is that it was intended to cover similar use cases, and I might just be missing enough knowledge on this to crack it. – Twelveg Oct 20 '22 at 21:55
  • Maybe if you describe what that SQL does? – Tom Harrington Oct 20 '22 at 23:22
  • Updated a bit and resorted the chunks, and added more explanations, to make more clear that it is not about the SQL itself - I am using the SQL to verify that my logic for the query is sound, and to give example of what exactly i want to achieve. – Twelveg Oct 21 '22 at 07:43
  • @Twelveg It's still not clear what you're trying to accomplish. What does that SQL do? What result do you want to get that you can't? – Tom Harrington Oct 21 '22 at 17:38
  • @TomHarrington I added the request, in a much simpler way, with what I am trying to accomplish, without the SQL and the code I already did as a separate part at the beginning; I was providing that just for people which do not want to construct everything themselves, as I am missing (IF even possible) only one tiny chunk of code how to chain NSExpression forKeyPath, forFunction, and some way to do a condition before forFunction is applied. The entire question has nothing to do with the SQL itself. – Twelveg Oct 22 '22 at 18:23
  • The short answer is you cannot execute any code or conditionals in a Core Data `NSExpression` chain. – Paulw11 Oct 23 '22 at 22:07
  • I beg to differ, there are NSExpression(format: < with custom expressions>), but if i use that i get some issues with the NSExpression(forFunction: ). But i expect it should be somehow possible to give to the NSDescriptor instead of the simple NSExpression(forFunction, keyvalue path) something like NSExpression(forFunction, NSExpression(format: condition i want)) - but the forFunction part complains then. – Twelveg Oct 24 '22 at 07:40
  • I'm pretty sure what you're asking for isn't possible in a Core Data fetch. You could post-process your fetch results, or you could drop Core Data for a SQLite wrapper like GRDB. Keep in mind that not every `NSExpression` is compatible with Core Data, so it's capabilities can be misleading as to what Core Data can do. – Tom Harrington Oct 24 '22 at 21:30
  • @TomHarrington: I was assuming as much, but hope dies last, hence my question posted :) – Twelveg Oct 26 '22 at 17:48

0 Answers0