2012-12-28 19 views
27

Próbuję nauczyć się używać Slick do kwerendy MySQL. Mam następujący typ kwerendy pracy, aby uzyskać pojedynczy obiekt wizyta:Scala zręczna kwerenda gdzie na liście

Q.query[(Int,Int), Visit](""" 
    select * from visit where vistor = ? and location_code = ? 
""").firstOption(visitorId,locationCode) 

Co chciałbym wiedzieć, w jaki sposób mogę zmienić powyższe kwerendy, aby uzyskać listę [Odwiedź] Dla kolekcji Lokalizacje. ... coś podobnego:

val locationCodes = List("loc1","loc2","loc3"...) 
Q.query[(Int,Int,List[String]), Visit](""" 
    select * from visit where vistor = ? and location_code in (?,?,?...) 
""").list(visitorId,locationCodes) 

Czy to możliwe przy pomocy Slick?

+0

Czy to nie działa? Powinno działać. –

+0

Nie możesz trzymać się Krotek wartości? Gwarantuje to, że liczba parametrów przekazywanych do zapytania jest stała. –

Odpowiedz

28

Jak sugeruje inna odpowiedź, jest to nieporęczne w przypadku zapytań statycznych. Statyczny interfejs zapytań wymaga opisania parametrów wiązania jako Product. (Int, Int, String*) nie jest poprawną wersją scala, a użycie (Int,Int,List[String]) wymaga również niektórych kludów. Ponadto, upewnienie się, że locationCodes.size jest zawsze równe liczbie (?, ?...), które masz w zapytaniu, jest kruche.

W praktyce nie stanowi to większego problemu, ponieważ zamiast tego chce się używać monady kwerendy, co jest bezpiecznym i zalecanym sposobem używania Slicka.

val visitorId: Int = // whatever 
val locationCodes = List("loc1","loc2","loc3"...) 
// your query, with bind params. 
val q = for { 
    v <- Visits 
    if v.visitor is visitorId.bind 
    if v.location_code inSetBind locationCodes 
    } yield v 
// have a look at the generated query. 
println(q.selectStatement) 
// run the query 
q.list 

to zakładając, że masz tabele skonfigurować tak:

case class Visitor(visitor: Int, ... location_code: String) 

object Visitors extends Table[Visitor]("visitor") { 
    def visitor = column[Int]("visitor") 
    def location_code = column[String]("location_code") 
    // .. etc 
    def * = visitor ~ .. ~ location_code <> (Visitor, Visitor.unapply _) 
} 

Należy pamiętać, że zawsze można owinąć zapytanie w metodzie.

def byIdAndLocations(visitorId: Int, locationCodes: List[String]) = 
    for { 
    v <- Visits 
    if v.visitor is visitorId.bind 
    if v.location_code inSetBind locationCodes 
    } yield v 
} 

byIdAndLocations(visitorId, List("loc1", "loc2", ..)) list 
+0

To wygląda obiecująco. Nie wracam do pracy do środy, ale postaram się znaleźć czas, aby przetestować to wcześniej i odpowiem. – ShatyUT

+0

Czy można również użyć s.th. podobnie jak Parametry [List [Inten]], a następnie zastąp "def byIdAndLocations" za pomocą QueryTemplate "val byIdAndLocations"? – longliveenduro

+0

Nie możesz użyć 'val ids = List (1,2,3) | val result: DBIO [Seq [T]] = query.filter (_. id inSet ids) 'per http://stackoverflow.com/questions/17408444/is-it-possible-to-use-in-clause-in -plain-sql-slick # comment52439626_17422901? –

5

To nie działa, ponieważ StaticQuery object (Q) spodziewa się domyślnie ustawić parametry w ciągu kwerendy przy użyciu parametrów rodzaj metody query aby stworzyć coś w rodzaju setter obiektu (typu scala.slick.jdbc.SetParameter[T]).
Rola SetParameter[T] polega na ustawieniu parametru zapytania na wartość typu T, gdzie wymagane typy są pobierane z parametrów typu query[...].

Z tego co widzę, nie ma takiego obiektu zdefiniowano dla T = List[A] dla rodzajowego A i wydaje się rozsądny wybór, ponieważ nie można właściwie napisać zapytanie SQL z dynamicznej listy parametrów dla IN (?, ?, ?,...) klauzuli


zrobiłem eksperyment poprzez zapewnienie takiej wartości niejawny przez następujący kod

import scala.slick.jdbc.{SetParameter, StaticQuery => Q} 

def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter { 
    case (seq, pp) => 
     for (a <- seq) { 
      pconv.apply(a, pp) 
     } 
} 

implicit val listSP: SetParameter[List[String]] = seqParam[String] 

z tym zakresie, powinny być w stanie wykonać swój kod

val locationCodes = List("loc1","loc2","loc3"...) 
Q.query[(Int,Int,List[String]), Visit](""" 
    select * from visit where vistor = ? and location_code in (?,?,?...) 
""").list(visitorId,locationCodes) 

Ale trzeba zawsze ręcznie zagwarantować, że rozmiar locationCodes jest taka sama jak liczba ? w IN klauzuli


W końcu uważam, że odkurzacz Rozwiązaniem mogłyby być tworzone za pomocą makra generalizować na typie sekwencji. Ale nie jestem pewien, czy byłby to mądry wybór dla frameworka, biorąc pod uwagę wyżej wymienione problemy z dynamiczną naturą wielkości sekwencji.

3

Można wygenerować w klauzuli automatycznie tak:

def find(id: List[Long])(implicit options: QueryOptions) = { 
    val in = ("?," * id.size).dropRight(1) 
    Q.query[List[Long], FullCard](s""" 
     select 
      o.id, o.name 
     from 
      organization o 
     where 
      o.id in ($in) 
     limit 
      ? 
     offset 
      ? 
      """).list(id ::: options.limits) 
    } 

I używać niejawny SetParameter jak pagoda_5b says

def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter { 
    case (seq, pp) => 
     for (a <- seq) { 
     pconv.apply(a, pp) 
     } 
    } 

    implicit def setLongList = seqParam[Long] 
2

Jeśli masz skomplikowany zapytanie, a do zrozumienia wymienionych powyżej nie jest opcja, możesz zrobić coś podobnego w Slick 3. Ale musisz upewnić się, że sprawdzasz dane w swoim parametrze zapytania listy, aby zapobiec iniekcji SQL:

val locationCodes = "'" + List("loc1","loc2","loc3").mkString("','") + "'" 
sql""" 
    select * from visit where visitor = $visitor 
    and location_code in (#$locationCodes) 
""" 

znak # przed zmiennej odniesienia wyłącza sprawdzanie typu i pozwala rozwiązać ten problem bez podawania funkcji po niejawna konwersja parametru lista zapytań.