Составление запросов Database.Esqueleto, условных объединений и подсчета
Как я могу составить Database.Esqueleto запросы модульным способом, так что после определения "базового" запроса и соответствующего набора результатов я могу ограничить набор результатов добавив дополнительные внутренние соединения и выражения.
Также, как я могу преобразовать базовый запрос, который возвращает список сущностей (или кортежей полей) в запрос, который подсчитывает набор результатов, поскольку базовый запрос не выполняется как таковой, а измененная версия его с LIMIT и OFFSET.
Следующий неправильный фрагмент кода Haskell, принятый из книги Yesod, надеюсь, разъясняет, к чему я стремился.
{-# 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 ()
Ответы
Ответ 1
Для LIMIT
и COUNT
, ответ hammar полностью прав, поэтому я не буду вникать в них. Я просто повторю, что после использования select
вы больше не сможете изменить запрос.
Для JOIN
s в настоящее время вы не можете выполнить INNER JOIN
с запросом, который был определен в другом from
(и (FULL|LEFT|RIGHT) OUTER JOIN
s). Однако вы можете делать неявные объединения. Например, если вы определили:
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
s. Если вам нужно сделать OUTER JOIN
, вам придется реорганизовать свой код, чтобы все OUTER JOIN
находились в одном и том же from
(обратите внимание, что вы можете сделать неявное соединение между OUTER JOIN
просто штрафом).
Ответ 2
Посмотрите документацию и тип 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
Это работает для ограничения и подсчета. Я еще не понял, как это сделать для присоединений, но похоже, что это должно быть возможно.