我正在写一个网络应用程序Yesod&持久性。我有一个包含几个表的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) |]
所以我希望你能得到帮助:
答案 0 :(得分:0)
所以我发现在我的情况下使用子查询要比加入快得多,如果需要你可以这样做:
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