2017-09-06 3 views
3

Ich schreibe eine Webapp mit Jessod & Persistent. Ich habe eine SQL-Datenbank mit mehreren Tabellen, die Eigenschaften meiner "Projekte" enthalten. Ich habe eine Haupttabelle und für die Option mit mehreren Werten zusätzliche Tabellen, die mit der ID verknüpft sind.Dynamisch SQL-Abfragen mit Esqueleto und Template Haskell erstellen?

Der Benutzer sollte in der Lage sein, die Eigenschaften auszuwählen, die er filtern und den Filterwert angeben möchte. Wenn die Benutzer Filter für das Betriebssystem, die Lizenz und die Codierung der SQL-Abfrage würde wie folgt aussehen:

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 

aber ich will nicht immer alle Tabellen verbinden, denn das ist sehr schlecht wäre für die Leistung, wenn Es gibt viele Tabellen, aber der Benutzer filtert nur auf ein paar. Aber ich möchte auch nicht für jede Kombination abfragbarer Features eine Abfrage schreiben, da das Schreiben von N² meist identische Abfragen bedeuten würde.

Die 'on' und 'where'-Klauseln können dynamisch ausgeführt werden, abhängig davon, ob wir filtern wollen oder nicht. Die Joins liegen jedoch innerhalb der Parameter der Lambda-Funktion. Ich habe keine Möglichkeit gefunden, dies abhängig von äußeren Variablen aufzubauen.

Also dachte ich Template Haskell könnte den Trick tun ... Ich begann TH zu lernen und den Kern der Abfrage in TH zu konvertieren. Aber jetzt bin ich fest und nicht sicher, ob TH mir helfen kann und ob es der richtige Weg ist?

So, hier ist meine Fortschritte mit 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 würde Ich mag Sie Hilfe sind:

  • Kann/Soll ich dies tun, mit Template Haskell?
  • Wenn ja: Wie kann ich die Funktion foo mit Argumenten aufrufen?
  • Wenn nicht: Was ist die richtige Lösung?
+0

Wenn Sie vorhaben, dies mit TH zu tun, dann brauchen Sie nicht eine einzige TH-Funktion für die gesamte 'runquery' Funktion zu schreiben - Sie nur eine TH-Funktion haben kann, die erzeugt ein Muster. Sie können also etwas wie 'from $ \ $ (mkInnerJoin [" l "," o "," c "]) -> ...' haben. TH ist wahrscheinlich nicht der beste Weg, dies zu tun (es ist der beste Weg, um ein paar Dinge zu tun ...); Sie könnten damit beginnen, einen Datentyp zu definieren, der die Struktur Ihrer Abfrage darstellt. – user2407038

Antwort

0

So fand ich heraus, dass die Verwendung von Unterabfragen in meinem Fall ist viel schneller als schließt sich sowieso und Sie können sie tun, wenn nötig:

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 

die große Menge an duplizierten Code zu reduzieren ich dann verwendet, 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 
Verwandte Themen