2013-05-16 1 views
11

"기본"쿼리와 해당 결과 집합을 정의한 후에 내부 조인을 추가하고 여기서 표현.Database.Esqueleto 쿼리, 조건부 조인 및 카운팅 작성

또한 기본 쿼리가 실행되지 않았으므로 결과 집합을 계산하는 쿼리로 엔티티 (또는 필드 튜플) 목록을 반환하는 기본 쿼리를 변환 할 수는 있지만 수정 된 버전은 LIMIT 및 OFFSET.

the Yesod Book에서 채택 된 다음 잘못된 Haskell 코드 스 니펫이 내가 의도 한 바를 명확하게 설명합니다. 문서 및 select의 유형을 보면

{-# 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() 

답변

7

LIMITCOUNT의 경우 hammar의 대답은 완전히 정확하므로 자세히 설명하지 않겠습니다. select을 사용하면 다시 어떤 식 으로든 쿼리를 변경할 수 없다는 점을 다시 한번 강조 할 것입니다. JOIN들에 대한

는, 현재는 다른 from (도 (FULL|LEFT|RIGHT) OUTER JOIN들)에 정의 된 쿼리와 INNER JOIN을 할 수 없습니다. 그러나 암시 적 조인을 수행 할 수 있습니다. 예를 들어, 정의한 경우 :

commentsQuery = 
    from $ \c -> do 
    (p, b) <- baseQuery 
    where_ (b ^. BlogPostId ==. c ^. CommentBlogPostId) 
    return (p, b, c) 

Esqueleto 다음의 라인을 따라 뭔가 생성합니다 : 다음

baseQuery = 
    from $ \(p `InnerJoin` b) -> do 
    on (p ^. PersonId ==. b ^. BlogPostAuthorId) 
    where_ (p ^. PersonName `like` val "J%") 
    return (p, b) 

당신은 말할 단지 수

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 사이의 암시 적 조인을 할 수 있음).

+1

간격을 메우고 결정적인 답을 제공해 주셔서 감사합니다. – Tero

+0

'commentsQuery'에서'from'을 사용하기 전에'baseQuery'를 사용할 수도 있습니다. –

+0

또한 잘못된 SQL 결과를 초래하는 esqueleto 쿼리를 버그로보고하여 해당 루트를 조사 할 수 있도록하십시오. 당신이 본 사람은 알려진 버그지만 수정되지 않은 버그 인'()'의 처리와 관련이 있습니다.회피책으로'return (val True)'와 같은 것을 할 수 있습니다. –

8

:

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 

이것은 제한 및 계산에 사용됩니다. 나는 조인을 위해 그것을하는 방법을 아직 이해하지 못했지만 가능한 것처럼 보입니다.

+0

문제를 부분적으로 해결해 주셔서 감사합니다. 내 유스 케이스에서는 조인도 작성해야하므로 당분간 원시 SQL 문자열을 연결하는 방법을 사용한다. – Tero

+3

건배 @hammar. 이 문제를 해결하는 데 어려움을 겪고있는 다른 모든 사람들을 위해 :이 Persistent 설정을위한 결과 값 타입은'[Value Int64]'이다. 나는. 멤버가 카운트 인 싱글 톤 목록으로 평가됩니다. –