2013-06-07 6 views
10

Ich versuche Slick zu verwenden, um eine Viele-zu-viele-Beziehung abzufragen, aber ich stoße auf eine Reihe von Fehlern, die am meisten hervorstechen: "Weiß nicht wie zum Auspacken (User, Skill) zu T und packen zu G ".Scala Slick: Probleme mit groupBy und fehlenden Shapes

Die Struktur der Tabellen ist ähnlich der folgenden:

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

Letztlich, was ich will ist

etwas von der Form
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 

erreichen, aber bevor ich verbringe die Zeit zu bekommen versuchen, group_concat, um auch zu arbeiten, habe ich versucht, die einfachere Abfrage zu produzieren (die ich glaube, dass noch gültig ist ...)

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

ich eine Vielzahl von scala Code versucht haben, diese Abfrage zu produzieren, aber ein Beispiel dafür, was oberhalb der Formfehler verursacht, ist

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

In ähnlicher Weise erzeugt die folgenden ein Packfehler in Bezug auf „User“ statt „(Benutzer, Geschicklichkeit)“

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

Wenn jemand irgendwelche Vorschläge hat, wäre ich sehr dankbar: ich habe die meisten von heute und gestern verbrachte Scheuern google/google Gruppen sowie die Slick-Quelle, aber ich habe nicht eine Lösung noch.

(Auch ich bin mit postgre so Group_concat tatsächlich string_agg wäre)

EDIT

So scheint es, wie wenn groupBy verwendet wird, wird die abgebildete Projektion angewendet, da so etwas wie

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

funktioniert gut, weil _._ 1 gibt den Typ Benutzer, die eine Shape seit Benutzer hat, ist eine Tabelle. Wenn wir jedoch xs.first aufrufen (wie wir es tun, wenn wir groupBy aufrufen), erhalten wir tatsächlich einen zugeordneten Projektionstyp (User, Skill), oder wenn wir map (_._ 1) zuerst anwenden, erhalten wir den Typ User, was ist nicht Benutzer! Soweit ich das beurteilen kann, gibt es keine Form mit Benutzer als Mischtyp, da die einzigen definierten Formen für Shape [Spalte [T], T, Spalte [T]] und für eine Tabelle T < sind: TableNode, Shape [T , NothingContainer # TableNothing, T] wie in slick.lifted.Shape definiert. Außerdem, wenn ich etwas tun, wie

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

bekomme ich einen seltsamen Fehler in der Form „NoSuchElementException: Schlüssel nicht gefunden: @ 1515100893“, wo die Zifferntaste Wert jedes Mal ändert. Dies ist nicht die Frage, die ich möchte, aber es ist trotzdem ein seltsames Problem.

+0

Ich schrieb eine direkte Abfrage in einer Situation, wenn ich mehrere Tabellen verbinden musste. Eine bessere performante Variante (vorausgesetzt, dass u.id, s.id, us.userId und us.skillId Schlüssel sind) könnte übrigens sein: "SELECT u. *, Group_concat (s.name) FROM user_skill us JOIN Fähigkeiten s ON us.skillId = s.id JOIN Benutzer u ON us.userId = u.id GROUP BY u.id " – Ashalynd

+0

Wie auch immer, wenn Sie gruppieren, erhalten Sie eine Karte (ID -> Liste ((u, s)) und das ist nicht genau wie es in deinem {case} Ausdruck beschrieben ist. – Ashalynd

Antwort

1

Ich habe gegen ähnliche Situationen gelaufen. Während ich gerne mit Scala und Slick arbeite, glaube ich, dass es Zeiten gibt, in denen es einfacher ist, ein Objekt in der Datenbank zu denormalisieren und die Slick-Tabelle mit einer Ansicht zu verknüpfen.

Zum Beispiel habe ich eine Anwendung, die ein Tree-Objekt hat, das in mehrere Datenbanktabellen normalisiert ist. Da ich mit SQL vertraut bin, denke ich, dass es eine sauberere Lösung als das Schreiben einer einfachen Scala Slick-Abfrage ist.Die Scala-Code:

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

und die darunter liegende (Postgres) Ansicht:

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

Versuchen Sie dies. Hoffe es kann ergeben, was Sie erwartet haben. Finden Sie den Slick-Code unter den Fallklassen.

click here für die Referenz in Bezug auf angehobene Einbettung.

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) 
Verwandte Themen