簡體   English   中英

使用Esqueleto和Template Haskell動態構建SQL查詢?

[英]Dynamically build SQL-Queries wit Esqueleto and Template Haskell?

我正在寫一個帶有Yesod和Persistent的webapp。 我有一個包含幾個表的SQL數據庫,其中包含我的“項目”的特征。 我有一個主表,對於具有多個值的選項,額外的表與id鏈接。

用戶應該能夠選擇他想要過濾的那些特征,並指定過濾器值。 如果用戶過濾os,許可證和SQL-Query的編碼將如下所示:

runquery :: (YesodPersist site, YesodPersistBackend site ~ SqlBackend) =>
             String -> String -> String
            -> HandlerT site IO [Entity Project]
runquery os license coding = runDB
  $ select $ distinct
  $ from $ \(p `InnerJoin` pl `InnerJoin` l `InnerJoin` pc
            `InnerJoin` c `InnerJoin` o `InnerJoin` po) -> do
     on $ p ^. ProjectId ==. pl ^. ProjectLicenseFkProjectId
     on $ p ^. ProjectId ==. pc ^. ProjectCodingFkProjectId
     on $ p ^. ProjectId ==. po ^. ProjectOsFkProjectId
     on $ l ^. LicenseId ==. pl ^. ProjectLicenseFkLicenseId
     on $ o ^. OsId      ==. po ^. ProjectOsFkOsId
     on $ c ^. CodingId  ==. pc ^. ProjectCodingFkCodingId
     where_ ( o ^. OsName ==. val (Just (pack os)))
     where_ ( l ^. LicenseName ==. val (Just (pack license)))
     where_ ( c ^. CodingName ==. val (Just (pack coding)))
     limit 50
     return p

但是我不想總是加入所有表,因為當有很多表但用戶只過濾幾個表時,性能會非常糟糕。 但我也不想為可查詢功能的每個組合編寫查詢,因為這意味着編寫N²大多數相同的查詢。

“on”和“where”子句可以動態完成,具體取決於我們是否要過濾。 但是連接是在Lambda函數的參數范圍內。 我發現無法構建依賴於外部變量的這種依賴。

所以我認為模板Haskell可以做到這一點......我開始學習TH並將查詢的核心轉換為TH。 但是現在我陷入困境並且不確定TH是否可以幫助我以及它是否是正確的方法?

所以這是我使用Template Haskell的進度:

foo os license coding = lamE [pat] (code)
    where
        p = mkName "p"
        po = mkName "po"
        pl = mkName "pc"
        pc = mkName "pl"
        pat = pat' [os, license, coding] [varP po, varP pl, varP pc]
        pat' []         []        = varP p
        pat' ((Just _):ws) (q:qs) = infixP q (mkName "InnerJoin") (pat' ws qs)
        pat' (Nothing:ws)  (q:qs) = pat' ws qs
        code = do
            case os of
              Just _ -> [|
                  on $ $(varE p) ^. ProjectId ==. $(varE po) ^. ProjectOsFkProjectId
                  |]
              Nothing -> [| return () |]
            case license of
              Just _ -> [|
                  on $ $(varE p) ^. ProjectId ==. $(varE pl) ^. ProjectLicenseFkProjectId
                  |]
              Nothing -> [| return () |]
            case coding of
              Just _ -> [|
                  on $ $(varE p) ^. ProjectId ==. $(varE pc) ^. ProjectCodingFkProjectId
                  |]
              Nothing -> [| return () |]
            [| do
            limit 50
            return $(varE p) |]

所以我希望你能得到幫助:

  • 可以/我應該使用Template Haskell嗎?
  • 如果是這樣:我如何用參數調用函數foo?
  • 如果不是:什么是正確的解決方案?

所以我發現在我的情況下使用子查詢比加入要快得多,如果需要你可以做到:

runquery os license coding = runDB
    $ select $ distinct
    $ from $ \p -> do
         case os of
           Just os' ->
             where_ $ p ^. ProjectId `in_`
               subList_select ( distinct $ from $
                 \(o `InnerJoin` po) -> do
                   on $ o ^. OsId       ==. po ^. ProjectOsOId
                   where_ $ o ^. OsName ==. val (Just $ pack os') 
                   return $ po ^. ProjectOsPId
                   )
           Nothing -> return ()
         case license of
           Just license' ->
             where_ $ p ^. ProjectId `in_`
               subList_select ( distinct $ from $
                 \(l `InnerJoin` pl) -> do
                   on $ l ^. LicenseId      ==. pl ^. ProjectLicenseLId
                   where_ $ l ^. LicenseName ==. val (Just $ pack license') 
                   return $ pl ^. ProjectLicensePId
                   )
           Nothing -> return ()
         -- ...
         limit 50
         return p

為了減少大量的重復代碼,我使用了Template-Haskell:

gencheck t = code
  where
    tableId       = mkName $ t ++ "Id"
    crosstableId  = mkName $ "Project" ++ t ++ "XId"
    crosstablePId = mkName $ "Project" ++ t ++ "PId"
    tableName     = mkName $ t ++ "Name"
    var           = mkName $ fmap toLower t
    code = [|
      case $(varE var) f of
           Just _filter ->
             where_ $ p ^. ProjectId `in_`
               subList_select ( distinct $ from $
                 \(o `InnerJoin` po) -> do
                   on     $ o  ^. $(conE tableId)   ==. po ^. $(conE crosstableId)
                   where_ $ o  ^. $(conE tableName) ==. val (Just _filter)
                   return $ po ^. $(conE crosstablePId)
                   )
           Nothing -> return ()
           |]

runquery f = runDB
    $ select $ distinct
    $ from $ \p -> do
         $(gencheck "Os")
         $(gencheck "Cat")
         $(gencheck "License")
         $(gencheck "Coding")
         $(gencheck "Gui")
         limit 50
         return p

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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