2013-06-07 7 views
10

Sto cercando di utilizzare Slick per eseguire una query di relazione molti-a-molti, ma sto correndo in una varietà di errori, il più importante è "Non so come per decomprimere (Utente, Abilità) in T e comprimere in G ".Scala Slick: problemi con groupBy e forme mancanti

La struttura delle tabelle è simile al seguente:

case class User(val name: String, val picture: Option[URL], val id: Option[UUID]) 
object Users extends Table[User]("users") { 
    def name = column[String]("name") 
    def picture = column[Option[URL]]("picture") 
    def id = column[UUID]("id") 
    def * = name ~ picture ~ id.? <> (User, User.unapply _) 
} 

case class Skill(val name: String, val id: Option[UUID]) 
object Skills extends Table[Skill]("skill") { 
    def name = column[String]("name") 
    def id = column[UUID]("id") 
    def * = name ~ id.? <> (Skill, Skill.unapply _) 
} 

case class UserSkill(val userId: UUID, val skillId: UUID, val id: Option[UUID]) 
object UserSkills extends Table[UserSkill]("user_skill") { 
    def userId = column[UUID]("userId") 
    def skillId = column[UUID]("skillId") 
    def id = column[UUID]("id") 
    def * = userId ~ skillId ~ id.? <> (UserSkill, UserSkill.unapply _) 
    def user = foreignKey("userFK", userId, Users)(_.id) 
    def skill = foreignKey("skillFK", skillId, Skills)(_.id) 
} 

In definitiva, quello che voglio ottenere è una sorta di forma

SELECT u.*, group_concat(s.name) FROM user_skill us, users u, skills s WHERE us.skillId = s.id && us.userId = u.id GROUP BY u.id 

ma prima di spendere il tempo cercando di ottenere group_concat per funzionare, ho cercato di produrre la query più semplice (che credo sia ancora valida ...)

SELECT u.* FROM user_skill us, users u, skills s WHERE us.skillId = s.id && us.userId = u.id GROUP BY u.id 

ho provato una varietà di codice scala per produrre questa domanda, ma un esempio di ciò che causa dell'errore forma sopra è

(for { 
    us <- UserSkills 
    user <- us.user 
    skill <- us.skill 
} yield (user, skill)).groupBy(_._1.id).map { case(_, xs) => xs.first } 

Analogamente, quanto segue produce un errore relativo imballaggio "Utente" invece di "(user, Abilità)"

(for { 
    us <- UserSkills 
    user <- us.user 
    skill <- us.skill 
} yield (user, skill)).groupBy(_._1.id).map { case(_, xs) => xs.map(_._1).first } 

Se qualcuno ha qualche suggerimento, sarei molto grata: ho passato la maggior parte di oggi e di ieri purga gruppi Google/Google e la fonte chiazza di petrolio, ma non ho una soluzione ancora.

(Inoltre, sto usando postgre così group_concat sarebbe in realtà string_agg)

EDIT

Così sembra come quando si usa groupBy, la proiezione mappato viene applicato perché qualcosa di simile

(for { 
    us <- UserSkills 
    u <- us.user 
    s <- us.skill 
} yield (u,s)).map(_._1) 

funziona correttamente perché _._ 1 fornisce il tipo Utenti, che ha una forma poiché Users è una tabella. Tuttavia, quando chiamiamo xs.first (come facciamo quando chiamiamo groupBy), in realtà otteniamo un tipo di proiezione mappato (User, Skill), o se applichiamo map (_._ 1) per primo, otteniamo il tipo User, che non sono utenti! Per quanto ne so, non c'è forma con Utente come tipo misto perché le uniche forme definite sono per Forma [Colonna [T], T, Colonna [T]] e per una tabella T <: TableNode, Shape [T , NothingContainer # TableNothing, T] come definito in slick.lifted.Shape. Inoltre, se faccio qualcosa di simile

(for { 
    us <- UserSkills 
    u <- us.user 
    s <- us.skill 
} yield (u,s)) 
    .groupBy(_._1.id) 
    .map { case (_, xs) => xs.map(_._1.id).first } 

ottengo uno strano errore di forma "NoSuchElementException: chiave non trovato: @ 1.515.100,893 mila", dove il valore tasto numerico cambia ogni volta. Questa non è la domanda che voglio, ma è comunque una strana questione.

+0

Ho finito per scrivere una query diretta in una situazione in cui dovevo unirmi a più tabelle. A proposito, una variante più performante (fornita da u.id, s.id, us.userId e us.skillId sono le chiavi) potrebbe essere: "SELECT u. *, Group_concat (s.name) FROM user_skill us JOIN skills s ON us.skillId = s.id JOIN utenti u ON us.userId = u.id GROUP BY u.id " – Ashalynd

+0

Ma comunque, quando si raggruppa, si ottiene una mappa (id -> Elenco ((u, s)) e questo è non esattamente come è descritto nell'espressione {case}. – Ashalynd

risposta

1

Ho incontrato anche situazioni simili. Sebbene mi piaccia lavorare con Scala e Slick, credo che ci siano momenti in cui è più facile denormalizzare un oggetto nel database stesso e collegare la Tabella Slick a una vista.

Ad esempio, ho un'applicazione che ha un oggetto Tree che è normalizzato in diverse tabelle di database. Dal momento che mi sento a mio agio con SQL, penso che sia una soluzione più pulita rispetto alla semplice scrittura di Scala Slick.Il codice Scala:

case class DbGFolder(id: String, 
        eTag: String, 
        url: String, 
        iconUrl: String, 
        title: String, 
        owner: String, 
        parents: Option[String], 
        children: Option[String], 
        scions: Option[String], 
        created: LocalDateTime, 
        modified: LocalDateTime) 
object DbGFolders extends Table[DbGFolder]("gfolder_view") { 
    def id = column[String]("id") 
    def eTag = column[String]("e_tag") 
    def url = column[String]("url") 
    def iconUrl = column[String]("icon_url") 
    def title = column[String]("title") 
    def owner = column[String]("file_owner") 
    def parents = column[String]("parent_str") 
    def children = column[String]("child_str") 
    def scions = column[String]("scion_str") 
    def created = column[LocalDateTime]("created") 
    def modified = column[LocalDateTime]("modified") 
    def * = id ~ eTag ~ url ~ iconUrl ~ title ~ owner ~ parents.? ~ 
      children.? ~ scions.? ~ created ~ modified <> (DbGFolder, DbGFolder.unapply _) 

    def findAll(implicit s: Session): List[GFolder] = { 
    Query(DbGFolders).list().map {v => 
     GFolder(id = v.id, 
       eTag = v.eTag, 
       url = v.url, 
       iconUrl = v.iconUrl, 
       title = v.title, 
       owner = v.owner, 
       parents = v.parents.map { parentStr => 
       parentStr.split(",").toSet }.getOrElse(Set()), 
       children = v.children.map{ childStr => 
       childStr.split(",").toSet }.getOrElse(Set()), 
       scions = v.scions.map { scionStr => 
       scionStr.split(",").toSet }.getOrElse(Set()), 
       created = v.created, 
       modified = v.modified) 
    } 
    } 
} 

E il sottostante (Postgres) Vista:

CREATE VIEW scion_view AS 
    WITH RECURSIVE scions(id, scion) AS (
     SELECT c.id, c.child 
     FROM children AS c 
     UNION ALL 
     SELECT s.id, c.child 
     FROM children AS c, scions AS s 
     WHERE c.id = s.scion) 
    SELECT * FROM scions ORDER BY id, scion;  

CREATE VIEW gfolder_view AS 
    SELECT 
    f.id, f.e_tag, f.url, f.icon_url, f.title, m.name, f.file_owner, 
    p.parent_str, c.child_str, s.scion_str, f.created, f.modified 
    FROM 
    gfiles AS f 
     JOIN mimes AS m ON (f.mime_type = m.name) 
     LEFT JOIN (SELECT DISTINCT id, string_agg(parent, ',' ORDER BY parent) AS parent_str 
       FROM parents GROUP BY id) AS p ON (f.id = p.id) 
     LEFT JOIN (SELECT DISTINCT id, string_agg(child, ',' ORDER BY child) AS child_str 
       FROM children GROUP BY id) AS c ON (f.id = c.id) 
     LEFT JOIN (SELECT DISTINCT id, string_agg(scion, ',' ORDER BY scion) AS scion_str 
       FROM scion_view GROUP BY id) AS s ON (f.id = s.id) 
    WHERE 
    m.category = 'folder'; 
2

Prova questo. Spero che possa produrre ciò che ti aspettavi. Trova il codice chiarificato sotto le classi dei casi.

click here per il riferimento relativo all'incasso sollevato.

case class User(val name: String, val picture: Option[URL], val id: Option[UUID]) 
      class Users(_tableTag: Tag) extends Table[User](_tableTag,"users") { 
       def name = column[String]("name") 
       def picture = column[Option[URL]]("picture") 
       def id = column[UUID]("id") 
       def * = name ~ picture ~ id.? <> (User, User.unapply _) 
      } 
      lazy val userTable = new TableQuery(tag => new Users(tag)) 

      case class Skill(val name: String, val id: Option[UUID]) 
      class Skills(_tableTag: Tag) extends Table[Skill](_tableTag,"skill") { 
       def name = column[String]("name") 
       def id = column[UUID]("id") 
       def * = name ~ id.? <> (Skill, Skill.unapply _) 
      } 
      lazy val skillTable = new TableQuery(tag => new Skills(tag)) 

      case class UserSkill(val userId: UUID, val skillId: UUID, val id: Option[UUID]) 
      class UserSkills(_tableTag: Tag) extends Table[UserSkill](_tableTag,"user_skill") { 
       def userId = column[UUID]("userId") 
       def skillId = column[UUID]("skillId") 
       def id = column[UUID]("id") 
       def * = userId ~ skillId ~ id.? <> (UserSkill, UserSkill.unapply _) 
       def user = foreignKey("userFK", userId, Users)(_.id) 
       def skill = foreignKey("skillFK", skillId, Skills)(_.id) 
      } 
      lazy val userSkillTable = new TableQuery(tag => new UserSkills(tag)) 






(for {((userSkill, user), skill) <- userSkillTable join userTable.filter on 
        (_.userId === _.id) join skillTable.filter on (_._1.skillId === _.id) 
       } yield (userSkill, user, skill)).groupBy(_.2.id)