2017-09-06 11 views
3

저는 웹 응용 프로그램을 작성하고 있습니다. Yesod & Persistent. 내 "프로젝트"의 특성을 포함하는 여러 테이블이있는 SQL 데이터베이스가 있습니다. 나는 메인 테이블을 가지고 있고, id를 가진 여러 테이블에 여분의 테이블을 가진 Option을 가지고있다.동적으로 SQL 쿼리를 Esqueleto와 Template Haskell로 빌드 하시겠습니까?

사용자는 원하는 특성의 마녀를 선택하여 필터 값을 지정할 수 있어야합니다. 운영 체제, 라이센스 및 코딩 SQL-쿼리에 대한 사용자 필터는 다음과 같이 할 경우 그 성능시기에 대한 아주 나쁜 것 때문에

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'- 절은 필터링 할 것인지 아닌지에 따라 동적으로 수행 할 수 있습니다. 그러나 조인은 람다 함수의 매개 변수 내에 있습니다. 외부 변수에 의존성을 구축 할 수있는 방법이 없습니다.

그래서 Template Haskell이 트릭을 할 수 있다고 생각했습니다. 저는 TH를 배우기 시작했고 TH의 핵심 부분을 TH로 바꾸기 시작했습니다. 하지만 지금은 붙어있어 TH가 나를 도울 수 있는지 여부와 올바른 방법인지 여부를 모르십니까?

  • /I는 템플릿 하스켈이 작업을 수행해야 할 수 :

    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) |] 
    

    그래서 내가 도움이있어 싶습니다 그래서 여기

    내 템플릿 하스켈로 진행입니까?

  • 만약 그렇다면 : 어떻게 인자로 함수 foo를 호출 할 수 있습니까?
  • 그렇지 않은 경우 : 올바른 해결책은 무엇입니까?
+0

TH로 이것을 수행하려면, 전체 runquery 함수에 하나의 TH 함수를 쓸 필요가 없습니다. TH 함수를 생성 할 수 있습니다 패턴. 그래서 $ \ $ (mkInnerJoin [ "l", "o", "c"]) -> ...'와 같은 것을 가질 수 있습니다. TH는 아마도 이것을 수행하는 최선의 방법은 아닙니다 (몇 가지 작업을 수행하는 가장 좋은 방법입니다 ...). 쿼리의 구조를 나타 내기 위해 데이터 유형을 정의하는 것으로 시작할 수 있습니다. – user2407038

답변

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 

난 후 사용하는 중복 된 코드의 큰 양을 줄이기 위해 템플릿 - 하스켈 :

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