2013-02-17 3 views
9

Sto cercando di produrre questo SQL con SLICK 1.0.0:Come scrivere query nidificate nel selezionare clausola

select 
    cat.categoryId, 
    cat.title, 
    (
     select 
     count(product.productId) 
     from 
     products product 
     right join products_categories productCategory on productCategory.productId = product.productId 
     right join categories c on c.categoryId = productCategory.categoryId 
     where 
     c.leftValue >= cat.leftValue and 
     c.rightValue <= cat.rightValue 
    ) as productCount 
from 
    categories cat 
where 
    cat.parentCategoryId = 2; 

Il mio tentativo di maggior successo è (ho lasciato cadere il "si unisce" parte, in modo che sia più leggibile):

def subQuery(c: CategoriesTable.type) = (for { 
     p <- ProductsTable 

     } yield(p.id.count)) 
     for { 
     c <- CategoriesTable 
     if (c.parentId === 2) 
     } yield(c.id, c.title, (subQuery(c).asColumn)) 

che produce l'SQL manca parentesi nella subquery:

select 
    x2.categoryId, 
    x2.title, 
    select count(x3.productId) from products x3 
    from 
    categories x2 
    where x2.parentCategoryId = 2 

che è ovviamente SQL valida 0.123.Qualche idea su come avere SLICK metti queste parentesi nel posto giusto? O forse c'è un modo diverso per raggiungere questo obiettivo?

+1

potete inserire al fianco dei tuoi tentativi? –

+0

Ho postato quello che ho ottenuto finora – wassertim

+0

Questo mi sembra un bug/svista nel compilatore di query per me. Forse dovresti presentare un bug report. –

risposta

11

Non ho mai usato Slick o ScalaQuery, quindi è stata una vera avventura scoprire come ottenere ciò. Slick è molto estensibile, ma la documentazione sull'estensione è un po 'complicata. Potrebbe già esistere, ma questo è quello che mi è venuto in mente. Se ho fatto qualcosa di sbagliato, correggimi.

Per prima cosa è necessario creare un driver personalizzato. Ho esteso lo H2Driver per poterlo testare facilmente.

trait CustomDriver extends H2Driver { 

    // make sure we create our query builder 
    override def createQueryBuilder(input: QueryBuilderInput): QueryBuilder = 
    new QueryBuilder(input) 

    // extend the H2 query builder 
    class QueryBuilder(input: QueryBuilderInput) extends super.QueryBuilder(input) { 

    // we override the expr method in order to support the 'As' function 
    override def expr(n: Node, skipParens: Boolean = false) = n match { 

     // if we match our function we simply build the appropriate query 
     case CustomDriver.As(column, LiteralNode(name: String)) => 
     b"(" 
     super.expr(column, skipParens) 
     b") as ${name}" 

     // we don't know how to handle this, so let super hanle it 
     case _ => super.expr(n, skipParens) 
    } 
    } 
} 

object CustomDriver extends CustomDriver { 
    // simply define 'As' as a function symbol 
    val As = new FunctionSymbol("As") 

    // we override SimpleSql to add an extra implicit 
    trait SimpleQL extends super.SimpleQL { 

    // This is the part that makes it easy to use on queries. It's an enrichment class. 
    implicit class RichQuery[T: TypeMapper](q: Query[Column[T], T]) { 

     // here we redirect our as call to the As method we defined in our custom driver 
     def as(name: String) = 
     CustomDriver.As.column[T](Node(q.unpackable.value), name) 
    } 
    } 

    // we need to override simple to use our version 
    override val simple: SimpleQL = new SimpleQL {} 
} 

Per utilizzarlo è necessario importare cose specifiche:

import CustomDriver.simple._ 
import Database.threadLocalSession 

Quindi, per utilizzarlo è possibile effettuare le seguenti (io ho usato le tabelle dalla documentazione ufficiale Slick nel mio esempio) .

// first create a function to create a count query 
def countCoffees(supID: Column[Int]) = 
    for { 
    c <- Coffees 
    if (c.supID === supID) 
    } yield (c.length) 

// create the query to combine name and count 
val coffeesPerSupplier = 
    for { 
    s <- Suppliers 
    } yield (s.name, countCoffees(s.id) as "test") 

// print out the name and count 
coffeesPerSupplier foreach { case (name, count) => 
    println(s"$name has $count type(s) of coffee") 
} 

Il risultato è questo:

Acme, Inc. has 2 type(s) of coffee 
Superior Coffee has 2 type(s) of coffee 
The High Ground has 1 type(s) of coffee 
+0

Funziona bene ora. Grazie mille! – wassertim