[英]Composing Database.Esqueleto queries, conditional joins and counting
如何以模块化方式编写Database.Esqueleto查询,以便在定义“基本”查询和相应的结果集之后,我可以通过添加其他内部联接和表达式来限制结果集。
此外,如何将返回实体(或字段元组)列表的基本查询转换为计算结果集的查询,因为基本查询不是这样执行的,而是使用LIMIT和OFFSET修改它的版本。
Yesod Book中采用的以下不正确的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 ()
查看文档和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
这适用于限制和计数。 我还没有弄清楚如何为连接做这件事,但它看起来应该是可能的。
对于LIMIT
和COUNT
,hammar的答案是完全正确的,所以我不会深入研究它们。 我将重申,一旦您使用select
您将无法再以任何方式更改查询。
对于JOIN
S,目前你是不是能够做一个INNER JOIN
与不同定义的一个查询from
(也不是(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 JOIN
完成了工作。 如果你需要进行OUTER JOIN
那么你将不得不重构你的代码,以便所有的OUTER JOIN
都是相同from
(注意你可以在OUTER JOIN
之间进行隐式连接)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.