简体   繁体   English

使用Esqueleto和Template Haskell动态构建SQL查询?

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

I'm writing a webapp wit Yesod & Persistent. 我正在写一个带有Yesod和Persistent的webapp。 I have a SQL-Database with several tables, containing characteristics of my "projects". 我有一个包含几个表的SQL数据库,其中包含我的“项目”的特征。 I have a main table and for the Option with multiple values extra tables linked with the id. 我有一个主表,对于具有多个值的选项,额外的表与id链接。

The user should be able to choose for witch of those characteristics he want's to filter and specify the filter value. 用户应该能够选择他想要过滤的那些特征,并指定过滤器值。 If the user filters for the os, the license and the coding the SQL-Query would look like this: 如果用户过滤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

but I don't want to join always all tables, since that would be very bad for performance when there are a lot of tables but the user filters only on a few. 但是我不想总是加入所有表,因为当有很多表但用户只过滤几个表时,性能会非常糟糕。 But I also don't want to write a Query for every combination of query-able features, since that would mean writing N² mostly identical queries. 但我也不想为可查询功能的每个组合编写查询,因为这意味着编写N²大多数相同的查询。

The 'on' and 'where'-clauses could be done dynamically depending on whether we want to filter or not. “on”和“where”子句可以动态完成,具体取决于我们是否要过滤。 But the joins are within the parameters of the Lambda-function. 但是连接是在Lambda函数的参数范围内。 I found no way to build this dependent on outer variables. 我发现无法构建依赖于外部变量的这种依赖。

So I thought Template Haskell might do the trick… I started learning TH and converting the core of the query to TH. 所以我认为模板Haskell可以做到这一点......我开始学习TH并将查询的核心转换为TH。 But now I'm stuck and not sure whether TH can help me and whether it is the right way? 但是现在我陷入困境并且不确定TH是否可以帮助我以及它是否是正确的方法?

So here is my progress with Template Haskell: 所以这是我使用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) |]

So I'd like you're help: 所以我希望你能得到帮助:

  • Can/Should I do this with Template Haskell? 可以/我应该使用Template Haskell吗?
  • If so: how can I call the function foo with arguments? 如果是这样:我如何用参数调用函数foo?
  • If not: what is the right solution? 如果不是:什么是正确的解决方案?

So I found out that using sub-queries is in my case much faster than joins anyway and you can do them if needed: 所以我发现在我的情况下使用子查询比加入要快得多,如果需要你可以做到:

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

To reduce the big amount of duplicated code I then used Template-Haskell: 为了减少大量的重复代码,我使用了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