Reputation: 871
Assume a simple 1 -> N relationship in your model.
I want to render a table with rows made of data from the main entity and an aggregated function of the dependant entity (the N), be it sum, avg, max or custom.
I have been trying dozens of handlers but did not find any that compiles...
Any hint?
Example:
Entity A
Key Value
a b
c d
e f
Entity B
ForeignKey Num Value
a 1 13.0
a 2 25.4
a 3 10.2
c 1 33.33
c 2 50.0
e 1 100.0
e 2 1000.0
And want to obtain Value from A and Value from B with max Num in the same "html row"
b 10.2
d 50.0
f 1000.0
Upvotes: 1
Views: 123
Reputation: 332
I'm not sure what have you already tried, but since this is practically a join (and persistent's type-safe API doesn't support them), running a custom SQL query should be the most efficient solution. If your model looks something like this
EntityA
key Text
value Text
deriving Show
EntityB
foreignKey EntityAId
num Int
value Double
deriving Show
a query for joining data with a max
function would look something like this:
SELECT a.value, max(b.num), b.value
FROM entity_a a LEFT OUTER JOIN entity_b b
ON a.id = b.foreign_key
GROUP BY (a.id);
Combining that with a rawSql function should do the trick.
If, however, you want to do it with persistent's type-safe API, it can result in a lot of queries to the database, depending on the size of your data. Here's how it could look like:
-- a custom maximum function for EntityB
customFunction :: [EntityB] -> Maybe EntityB
customFunction = foldr customFold Nothing
customFold :: EntityB -> Maybe EntityB -> Maybe EntityB
customFold a Nothing = Just a
customFold a (Just b) = if (entityBNum a) > (entityBNum b) then (Just a) else (Just b)
main :: IO ()
main = runSqlite ":memory:" $ do
runMigration migrateAll
-- insert some data
firstAId <- insert $ EntityA "a" "b"
secondAId <- insert $ EntityA "c" "d"
thirdAId <- insert $ EntityA "e" "f"
-- let's put this one just for demonstration
fourthAId <- insert $ EntityA "g" "h"
bId1 <- insert $ EntityB firstAId 1 13.0
bId2 <- insert $ EntityB firstAId 2 25.4
bId3 <- insert $ EntityB firstAId 3 10.2
bId4 <- insert $ EntityB secondAId 1 33.33
bId5 <- insert $ EntityB secondAId 2 50.0
bId6 <- insert $ EntityB thirdAId 1 100.0
bId7 <- insert $ EntityB thirdAId 2 1000.0
-- select all EntityA's
as <- selectList [] [Asc EntityAId]
-- aKeys are used as foreign keys for EntityB's
let aKeys = map entityKey as
-- these values will be used for "joining" data together
aVals = map (entityAValue . entityVal) as
-- this will produce a number of queries which is
-- equal to a number of groups (in your simple case, 3)
bs <- mapM (\bKey -> selectList [EntityBForeignKey ==. bKey] []) aKeys
-- extract what's needed from a list of lists of EntityB's
let bEntities = map (customFunction . (map entityVal)) bs
-- ... and zip them together
joined = zip aVals bEntities
liftIO $ print joined
The output I get for this piece of code is:
[("b",Just (EntityB {entityBForeignKey = Key {unKey = PersistInt64 1}, entityBNum = 3, entityBValue = 10.2})),("d",Just (EntityB {entityBForeignKey = Key {unKey = PersistInt64 2}, entityBNum = 2, entityBValue = 50.0})),("f",Just (EntityB {entityBForeignKey = Key {unKey = PersistInt64 3}, entityBNum = 2, entityBValue = 1000.0})),("h",Nothing)]
Extracting the table like in your question and putting it into Handler monad should be straightforward now.
I'm aware that this is not the prettiest piece of code to look at, but it may be useful for you to compare the two approaches shown here.
Upvotes: 2