首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >简化持久& Esqueleto代码

简化持久& Esqueleto代码
EN

Stack Overflow用户
提问于 2015-04-27 16:32:10
回答 1查看 477关注 0票数 0

我有一个相当简单的查询,它执行两个外部联接。(一顿饭有很多食谱,而食谱又有很多食物)。

代码语言:javascript
复制
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个元组。

代码语言:javascript
复制
(m1, r1, f1)
(m1, r1, f2)
(m1, r2, f3)
(m1, r2, f4)

我想将这些回滚回单个Meal数据类型。(这里简化为显示结构,当然还有其他字段存储在DB中)。

代码语言:javascript
复制
data Meal   = Meal   { recipes :: [Recipe] }
data Recipe = Recipe { foods :: [Food]   }
data Food   = Food   { name :: String }

我似乎必须完全手工地进行合并,而对于这个查询,它最终是2页左右的代码。

忽略了不应该像这样使用类型类这一事实,它看起来像是(愚蠢的)类型类DeserializeDb的许多实例。

代码语言:javascript
复制
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)

代码语言:javascript
复制
instance DeserializeDb (Entity DbFood) Food where
  deserializeDb (Entity _ val) = Food (dbFoodName val)

问题:

我唯一想公开的是查询签名。其余的都是实现垃圾。使用我没有注意到的持久性有什么窍门吗?是否必须手动将联接合并回haskell类型?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-05-05 03:33:36

由于@JPMoresmau的暗示,我最终得到了一个更短的,我认为更简单的方法。由于nub的原因,大型数据集的返回速度可能较慢,但对于小型数据集,返回速度要比我所需要的要快得多。

我仍然讨厌我有如此多的人工管道来从数据库返回的数据中构建树结构。我想知道是否有一个好的方法来做这个一般性的?

代码语言:javascript
复制
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)
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29901261

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档