2013-05-16 8 views
11

Database.Esqueletoクエリをモジュール式で構成するにはどうすれば "ベース"クエリとそれに対応する結果セットを定義した後で追加の内部結合を追加して結果セットを制限できるどこの表現。Database.Esqueletoクエリの作成、条件付き結合とカウント

また、エンティティ(またはフィールドタプル)のリストを返すベースクエリを、ベースクエリがそのまま実行されるのではなく結果セットをカウントするクエリに変換するにはどうすればよいですか?LIMITおよびオフセット。

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については、ハンマーの答えはまったく正しいので、私はそれらについて掘り下げません。 selectを使用すると、再度クエリを変更することはできません。 JOIN秒間

、現在、あなたは別のfrom(も(FULL|LEFT|RIGHT) OUTER JOIN S)で定義されたクエリでINNER JOINを行うことができません。ただし、暗黙の結合を行うことができます。あなたが定義した場合たとえば、:

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 

ないかなりが、取得します。

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

Esqueletoは、その後の線に沿って何かを生成します。 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

乾杯@ハンマー。この問題を解決するためにcountingQueryをコンパイルするには、結果の型を指定しなければなりませんでした。これは、(永続的な構成では) '[Value Int64]'です。私。メンバがカウントであるシングルトンリストに評価されます。 –