2015-03-21 16 views
7

Ho creato una raccolta many-to-many con Slick 3.0, ma sto cercando di recuperare i dati nel modo desiderato.Slick 3.0 query many-to-many con join come iterable

C'è una relazione molti-a-molti tra Eventi e Interessi. Qui sono le mie tabelle:

case class EventDao(title: String, 
        id: Option[Int] = None) 


class EventsTable(tag: Tag) 
    extends Table[EventDao](tag, "events") { 

    def id = column[Int]("event_id", O.PrimaryKey, O.AutoInc) 
    def title = column[String]("title") 

    def * = (
    title, 
    id.?) <> (EventDao.tupled, EventDao.unapply) 

    def interests = EventInterestQueries.query.filter(_.eventId === id) 
    .flatMap(_.interestFk) 
} 


object EventQueries { 

    lazy val query = TableQuery[EventsTable] 

    val findById = Compiled { k: Rep[Int] => 
    query.filter(_.id === k) 
    } 
} 

Ecco EventsInterests:

case class EventInterestDao(event: Int, interest: Int) 


class EventsInterestsTable(tag: Tag) 
    extends Table[EventInterestDao](tag, "events_interests") { 

    def eventId = column[Int]("event_id") 
    def interestId = column[Int]("interest_id") 

    def * = (
    eventId, 
    interestId) <> (EventInterestDao.tupled, EventInterestDao.unapply) 

    def eventFk = foreignKey("event_fk", eventId, EventQueries.query)(e => e.id) 
    def interestFk = foreignKey("interest_fk", interestId, InterestQueries.query)(i => i.id) 
} 


object EventInterestQueries { 
    lazy val query = TableQuery[EventsInterestsTable] 
} 

E infine Interessi:

case class InterestDao(name: String, 
         id: Option[Int] = None) 

class InterestsTable(tag: Tag) 
    extends Table[InterestDao](tag, "interests") { 

    def id = column[Int]("interest_id", O.PrimaryKey, O.AutoInc) 
    def name = column[String]("name") 
    def name_idx = index("idx_name", name, unique = true) 

    def * = (
    name, 
    id.?) <> (InterestDao.tupled, InterestDao.unapply) 

    def events = EventInterestQueries.query.filter(_.interestId === id) 
    .flatMap(_.eventFk) 
} 


object InterestQueries { 

    lazy val query = TableQuery[InterestsTable] 

    val findById = Compiled { k: Rep[Int] => 
    query.filter(_.id === k) 
    } 
} 

posso interrogare e recuperare tuple di (event.name, interessi) con il seguente :

val eventInterestQuery = for { 
    event <- EventQueries.query 
    interest <- event.interests 
} yield (event.title, interest.name) 

Await.result(db.run(eventInterestQuery.result).map(println), Duration.Inf) 

Quindi questo è quello che attualmente ho.

Quello che voglio è quello di essere in grado di compilare una classe caso come:

case class EventDao(title: String, 
       interests: Seq[InterestDao], 
       id: Option[Int] = None) 

Il guaio è che se aggiorno la mia classe caso come questo, si scombina il mio def * proiezione EventsTable. Inoltre, dovrò rinominare il filtro EventsTable.interests con qualcosa come EventsTable.interestIds che è un po 'brutto ma potrei vivere con se necessario.

Inoltre, non riesco a trovare un modo di scrivere una query for che produce (event.name, Seq(interest.name)). Ad ogni modo, è solo un trampolino di lancio per me essere in grado di cedere una tupla (EventDao, Seq(InterestDao)) che è ciò che voglio davvero tornare.

Qualcuno sa come posso ottenere queste cose? Voglio anche essere in grado di "prendere" un certo numero di Interessi, quindi per alcune query tutti verrebbero restituiti, ma per altri solo i primi 3 sarebbero.

risposta

6

Così, dopo aver letto this page e chiacchierando sulla mailing list, ho finalmente capito di lavoro:

val eventInterestQuery = for { 
    event <- EventQueries.query 
    interest <- event.interests 
} yield (event, interest) 

Await.result(db.run(eventInterestQuery.result 
    // convert the interests to a sequence. 
    .map { 
    _.groupBy(_._1) 
    .map { 
     case (k,v) => (k, v.map(_._2)) 
    }.toSeq 
} 
), Duration.Inf) 
+8

È impazzito, è necessario inviare * l'intero set di risultati non raggruppato * sul filo * e quindi * sul client. Per piccoli set di risultati, certo, non un grosso problema, ma l'invio di record di 100K + che potrebbero facilmente essere raggruppati a livello di database, wow. – virtualeyes

+1

@virtualeyes: quale approccio consiglieresti allora? – LuGo

+1

@virtualeyes Non ho letto la domanda completa ma il gruppo non può essere usato direttamente nella query? Come descritto nella documentazione: http://slick.typesafe.com/doc/3.0.0/queries.html#aggregation – Ixx

1

L'unico problema con groupBy è perdi ordine. Puoi piegare il risultato. Ho scritto questo helper per il mio progetto attuale:

def foldOneToMany[A, B](in: Seq[(A, Option[B])], eq: (A, B) => Boolean) 
         (f: (A, B) => A): Seq[A] = 
    in.foldLeft(List.empty[A]) { 
    case (head :: tail, (_, Some(rel))) if eq(head, rel) => 
     f(head, rel) :: tail 
    case (r, (el, Some(rel))) => f(el, rel) :: r 
    case (r, (el, None)) => el :: r 
    }.reverse 

Potrebbe fare con un po 'd'amore. Ora prende una funzione A, B => Boolean per determinare se B appartiene a A e una funzione A, B => A che aggiunge B a A.

Virtualeyes ha anche un punto. In Postgres è possibile utilizzare la funzione array_agg per utilizzare un po 'meno larghezza di banda dal db.