简体   繁体   English

编写Database.Esqueleto查询,条件连接和计数

[英]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. 对于LIMITCOUNT ,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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM