小编典典

组成Database.Esqueleto查询,条件联接和计数

sql

如何以模块化方式构成Database.Esqueleto查询,以便在定义“基本”查询和相应的结果集之后,可以通过添加其他内部联接和where表达式来限制结果集。

另外,由于基本查询不是照此执行的,而是使用LIMIT和OFFSET的修改版本,因此如何将返回实体列表(或字段元组)的基本查询转换为对结果集进行计数的查询。

Yesod书中采用的以下不正确的Haskell代码段有望阐明我的目标。

{-# 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 ()

阅读 161

收藏
2021-03-23

共1个答案

小编典典

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

对于JOINs,当前您无法INNER JOIN使用在其他from(nor (FULL|LEFT|RIGHT) OUTER 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的工作。如果需要执行a,OUTER JOIN则必须重构代码,以使所有OUTERJOINs都相同from(请注意,您可以在OUTER JOINs之间进行隐式联接就可以了)。

2021-03-23