[英]Composing Database.Esqueleto queries, conditional joins and counting
How can I compose Database.Esqueleto queries in a modular way such that after defining a "base" query and the corresponding result set, I can restrict the result set by adding additional inner joins and where expressions. 如何以模块化方式编写Database.Esqueleto查询,以便在定义“基本”查询和相应的结果集之后,我可以通过添加其他内部联接和表达式来限制结果集。
Also, how can I convert the base query that returns a list of entities (or field tuples) into a query that counts the result set since the base query is not executed as such, but a modified version of it with LIMIT and OFFSET. 此外,如何将返回实体(或字段元组)列表的基本查询转换为计算结果集的查询,因为基本查询不是这样执行的,而是使用LIMIT和OFFSET修改它的版本。
The following incorrect Haskell code snippet adopted from the Yesod Book hopefully clarifies what I'm aiming at. 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 ()
Looking at the documentation and the type of select
: 查看文档和select
的类型:
select :: (...) => SqlQuery a -> SqlPersistT m [r]
It's clear that upon calling select
, we leave the world of pure composable queries ( SqlQuery a
) and enter the world of side effects ( SqlPersistT m [r]
). 很明显,在调用select
,我们离开了纯组合查询的世界( SqlQuery a
)并进入副作用世界( SqlPersistT m [r]
)。 So we simply need to compose before we select
. 所以我们只需要在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
This works for limiting and counting. 这适用于限制和计数。 I haven't figured out how to do it for joins yet, but it looks like it should be possible. 我还没有弄清楚如何为连接做这件事,但它看起来应该是可能的。
For LIMIT
and COUNT
, hammar's answer is entirely correct so I'll not delve into them. 对于LIMIT
和COUNT
,hammar的答案是完全正确的,所以我不会深入研究它们。 I'll just reiterate that once you use select
you'll not be able to change the query in any way again. 我将重申,一旦您使用select
您将无法再以任何方式更改查询。
For JOIN
s, currently you are not able to do a INNER JOIN
with a query that was defined in a different from
(nor (FULL|LEFT|RIGHT) OUTER JOIN
s). 对于JOIN
S,目前你是不是能够做一个INNER JOIN
与不同定义的一个查询from
(也不是(FULL|LEFT|RIGHT) OUTER JOIN
或多个)。 However, you can do implicit joins. 但是,您可以执行隐式连接。 For example, if you have defined: 例如,如果您已定义:
baseQuery =
from $ \(p `InnerJoin` b) -> do
on (p ^. PersonId ==. b ^. BlogPostAuthorId)
where_ (p ^. PersonName `like` val "J%")
return (p, b)
Then you may just say: 然后你可以说:
commentsQuery =
from $ \c -> do
(p, b) <- baseQuery
where_ (b ^. BlogPostId ==. c ^. CommentBlogPostId)
return (p, b, c)
Esqueleto then will generate something along the lines of: 然后,Esqueleto将产生以下内容:
SELECT ...
FROM Comment, Person INNER JOIN BlogPost
ON Person.id = BlogPost.authorId
WHERE Person.name LIKE "J%"
AND BlogPost.id = Comment.blogPostId
Not pretty but gets the job done for INNER JOIN
s. 不漂亮但是为INNER JOIN
完成了工作。 If you need to do a OUTER JOIN
then you'll have to refactor your code so that all the OUTER JOIN
s are in the same from
(note that you can do an implicit join between OUTER JOIN
s just fine). 如果你需要进行OUTER JOIN
那么你将不得不重构你的代码,以便所有的OUTER JOIN
都是相同from
(注意你可以在OUTER JOIN
之间进行隐式连接)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.