簡體   English   中英

編寫Database.Esqueleto查詢,條件連接和計數

[英]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

這適用於限制和計數。 我還沒有弄清楚如何為連接做這件事,但它看起來應該是可能的。

對於LIMITCOUNT ,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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM