[英]Simplifying Persistent & Esqueleto code
我有一个相当简单的查询,它执行两个外部联接。 (一顿饭有很多食谱,反过来又有很多食物)。
getMeals :: (MonadIO m) => Key DbUser -> SqlPersistT m [Meal]
getMeals user =
fmap deserializeDb $ E.select $
E.from $ \(m `E.InnerJoin` u `E.LeftOuterJoin` r `E.LeftOuterJoin` f) -> do
E.on (r ?. DbRecipeId E.==. f ?. DbFoodRecipeId)
E.on (E.just (m ^. DbMealId) E.==. r ?. DbRecipeMealId)
E.on (m ^. DbMealUserId E.==. u ^. DbUserId)
E.where_ (m ^. DbMealUserId E.==. E.val user )
return (m, r, f)
这个查询很棒,它说明了它的需求,仅此而已。 但是,由于SQL的工作原理,它为我返回了一个表,其中包含每个重复的饭菜,每个匹配的外部联接。
例如,一顿饭有两个食谱,每个都有两种食物,就变成了4个元组。
(m1, r1, f1)
(m1, r1, f2)
(m1, r2, f3)
(m1, r2, f4)
我想将这些备份汇总为一个Meal
数据类型。 (这里简化为显示结构,其他字段当然存储在DB中)。
data Meal = Meal { recipes :: [Recipe] }
data Recipe = Recipe { foods :: [Food] }
data Food = Food { name :: String }
我似乎必须完全手动进行合并,最终此查询只有2页左右的代码页。
忽略了不应该这样使用类型类的事实,它看起来像很多(愚蠢的)类型类DeserializeDb
的实例:
class DeserializeDb a r | a -> r where
deserializeDb :: a -> r
instance DeserializeDb [(Entity DbMeal, Maybe (Entity DbRecipe))] [Meal] where
deserializeDb items = let grouped = groupBy (\a b -> entityKey (fst a) == entityKey (fst b)) items
joined = map (\list -> ( (fst . head) list
, mapMaybe snd list
)) grouped
in (map deserializeDb joined)
各种复杂情况的快照(代码: https ://gist.github.com/cschneid/2989057ec4bb9875e2ae)
instance DeserializeDb (Entity DbFood) Food where
deserializeDb (Entity _ val) = Food (dbFoodName val)
我唯一想公开的是查询签名。 剩下的就是实现垃圾。 我没有注意到使用“持久性”的窍门吗? 我是否必须手动将联接合并回haskell类型?
多亏@JPMoresmau的提示,我最终变得更短了,而且我认为方法更简单。 由于nub
,在大型数据集上它可能会比较慢,但是在小型数据集上,它返回的速度比我需要的要快得多。
我仍然讨厌我有太多手动管道来从数据库返回的数据中构建树形结构。 我想知道是否有一个很好的方法可以做到这一点?
module Grocery.Database.Calendar where
import Grocery.DatabaseSchema
import Grocery.Types.Meal
import Grocery.Types.Recipe
import Grocery.Types.Food
import Database.Persist
import Database.Persist.Sqlite
import qualified Database.Esqueleto as E
import Database.Esqueleto ((^.), (?.))
import Data.Time
import Control.Monad.Trans -- for MonadIO
import Data.List
import Data.Maybe
import Data.Tuple3
getMeals :: (MonadIO m) => Key DbUser -> SqlPersistT m [Meal]
getMeals user =
fmap deserializeDb $ E.select $
E.from $ \(m `E.InnerJoin` u `E.LeftOuterJoin` r `E.LeftOuterJoin` f) -> do
E.on (r ?. DbRecipeId E.==. f ?. DbFoodRecipeId)
E.on (E.just (m ^. DbMealId) E.==. r ?. DbRecipeMealId)
E.on (m ^. DbMealUserId E.==. u ^. DbUserId)
E.where_ (m ^. DbMealUserId E.==. E.val user )
return (m, r, f)
deserializeDb :: [(Entity DbMeal, Maybe (Entity DbRecipe), Maybe (Entity DbFood))] -> [Meal]
deserializeDb results = makeMeals results
where
makeMeals :: [(Entity DbMeal, Maybe (Entity DbRecipe), Maybe (Entity DbFood))] -> [Meal]
makeMeals dupedMeals = map makeMeal (nub $ map fst3 dupedMeals)
makeMeal :: Entity DbMeal -> Meal
makeMeal (Entity k m) = let d = dbMealDay m
n = dbMealName m
r = makeRecipesForMeal k
in Meal Nothing (utctDay d) n r
makeRecipesForMeal :: Key DbMeal -> [Recipe]
makeRecipesForMeal mealKey = map makeRecipe $ appropriateRecipes mealKey
appropriateRecipes :: Key DbMeal -> [Entity DbRecipe]
appropriateRecipes mealKey = nub $ filter (\(Entity _ v) -> dbRecipeMealId v == mealKey) $ mapMaybe snd3 results
makeRecipe :: Entity DbRecipe -> Recipe
makeRecipe (Entity k r) = let n = dbRecipeName r
f = makeFoodForRecipe k
in Recipe Nothing n f
makeFoodForRecipe :: Key DbRecipe -> [Food]
makeFoodForRecipe rKey = map makeFood $ appropriateFoods rKey
appropriateFoods :: Key DbRecipe -> [Entity DbFood]
appropriateFoods rKey = nub $ filter (\(Entity _ v) -> dbFoodRecipeId v == rKey) $ mapMaybe thd3 results
makeFood :: Entity DbFood -> Food
makeFood (Entity _ f) = Food (dbFoodName f)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.