【问题标题】:Composing Database.Esqueleto queries, conditional joins and counting编写 Database.Esqueleto 查询、条件连接和计数
【发布时间】:2013-05-11 20:17:37
【问题描述】:

如何以模块化方式编写 Database.Esqueleto 查询,以便在定义“基本”查询和相应的结果集后,我可以通过添加额外的内部连接和 where 表达式来限制结果集。

此外,我如何将返回实体列表(或字段元组)的基本查询转换为计算结果集的查询,因为基本查询不是这样执行的,而是使用 LIMIT 的修改版本和偏移量。

the Yesod Book 采用的以下不正确的 Haskell 代码 sn-p 希望能阐明我的目标。

{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies, OverloadedStrings #-}
{-# LANGUAGE GADTs, FlexibleContexts #-}
import qualified Database.Persist as P
import qualified Database.Persist.Sqlite as PS
import Database.Persist.TH
import Control.Monad.IO.Class (liftIO)
import Data.Conduit
import Control.Monad.Logger
import Database.Esqueleto
import Control.Applicative

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
    name String
    age Int Maybe
    deriving Show
BlogPost
    title String
    authorId PersonId
    deriving Show
Comment
    comment String
    blogPostId BlogPostId
|]

main :: IO ()
main = runStdoutLoggingT $ runResourceT $ PS.withSqliteConn ":memory:" $ PS.runSqlConn $ do
    runMigration migrateAll

    johnId <- P.insert $ Person "John Doe" $ Just 35
    janeId <- P.insert $ Person "Jane Doe" Nothing

    jackId <- P.insert $ Person "Jack Black" $ Just 45
    jillId <- P.insert $ Person "Jill Black" Nothing

    blogPostId <- P.insert $ BlogPost "My fr1st p0st" johnId
    P.insert $ BlogPost "One more for good measure" johnId
    P.insert $ BlogPost "Jane's" janeId

    P.insert $ Comment "great!" blogPostId

    let baseQuery = select $ from $ \(p `InnerJoin` b) -> do 
        on (p ^. PersonId ==. b ^. BlogPostAuthorId)
        where_ (p ^. PersonName `like` (val "J%"))
        return (p,b)

    -- Does not compile
    let baseQueryLimited = (,) <$> baseQuery <*> (limit 2)

    -- Does not compile
    let countingQuery = (,) <$> baseQuery <*> (return countRows)

    -- Results in invalid SQL 
    let commentsQuery = (,) <$> baseQuery
                <*> (select $ from $ \(b `InnerJoin` c) -> do
                        on (b ^. BlogPostId ==. c ^. CommentBlogPostId)
                        return ())

    somePosts <- baseQueryLimited
    count <- countingQuery
    withComments <- commentsQuery
    liftIO $ print somePosts
    liftIO $ print ((head count) :: Value Int)
    liftIO $ print withComments
    return ()

【问题讨论】:

    标签: sql haskell yesod


    【解决方案1】:

    查看文档和select的类型:

    select :: (...) => SqlQuery a -> SqlPersistT m [r]
    

    很明显,在调用select 时,我们离开了纯可组合查询的世界 (SqlQuery a),进入了副作用的世界 (SqlPersistT m [r])。所以我们只需要在select之前作曲。

    let baseQuery = from $ \(p `InnerJoin` b) -> do 
          on (p ^. PersonId ==. b ^. BlogPostAuthorId)
          where_ (p ^. PersonName `like` (val "J%"))
          return (p,b)
    
    let baseQueryLimited = do r <- baseQuery; limit 2; return r
    let countingQuery    = do baseQuery; return countRows
    
    somePosts <- select baseQueryLimited
    count     <- select countingQuery
    

    这适用于限制和计数。我还没有弄清楚如何为连接做这件事,但看起来应该是可能的。

    【讨论】:

    • 感谢您部分解决问题。在我的用例中,我也需要编写连接,所以暂时我将求助于连接原始 SQL 字符串。
    • 干杯@hammar。对于其他在编译该版本的countingQuery 版本时遇到问题的人:我必须指定结果类型,(对于我的Persistent 配置)是[Value Int64]。 IE。它评估为一个单例列表,其成员是计数。
    【解决方案2】:

    对于LIMITCOUNT,hammar 的回答是完全正确的,所以我不会深入研究它们。我只是重申一下,一旦您使用select,您将无法再以任何方式更改查询。

    对于JOINs,目前您无法对在不同from(也不是(FULL|LEFT|RIGHT) OUTER JOINs)中定义的查询执行INNER JOIN。但是,您可以进行隐式连接。例如,如果您定义了:

    baseQuery = 
      from $ \(p `InnerJoin` b) -> do 
      on (p ^. PersonId ==. b ^. BlogPostAuthorId)
      where_ (p ^. PersonName `like` val "J%")
      return (p, b)
    

    那你可以说:

    commentsQuery = 
      from $ \c -> do
      (p, b) <- baseQuery
      where_ (b ^. BlogPostId ==. c ^. CommentBlogPostId)
      return (p, b, c)
    

    Esqueleto 然后会生成类似以下内容的内容:

    SELECT ...
    FROM Comment, Person INNER JOIN BlogPost
    ON    Person.id = BlogPost.authorId
    WHERE Person.name LIKE "J%"
    AND   BlogPost.id = Comment.blogPostId
    

    不漂亮,但为INNER JOINs 完成了工作。如果您需要执行OUTER JOIN,那么您将不得不重构您的代码,以便所有OUTER JOINs 都在同一个from 中(请注意,您可以在OUTER JOINs 之间进行隐式连接就好了) .

    【讨论】:

    • 感谢您填补空白并提供结论性答案。
    • 我还应该注意,在commentsQuery 中,您也可以在使用from 之前使用baseQuery
    • 另外,请将任何导致无效 SQL 的 esqueleto 查询报告为错误,以便我们调查其根源。您看到的与() 的处理有关,这是一个已知但未修复的错误。作为一种解决方法,您可以执行诸如 return (val True) 之类的操作。
    • esqueleto-1.2.1 应该可以解决您使用 return () 的问题,而无需任何解决方法 =)。
    • 感谢其他提示和错误修复。我在标题中提到的“条件连接”意味着我有许多连接可能会或可能不会在查询中用于过滤结果。现在看来内部连接就足够了,所以我可以使用隐式连接语法。但是,我无法按照您的描述组合它们,因为下一个查询组合步骤需要指定上一个查询的实体,例如“(p,b)
    猜你喜欢
    • 2021-05-22
    • 1970-01-01
    • 1970-01-01
    • 2020-04-05
    • 2021-06-14
    • 1970-01-01
    • 2014-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多