I cannot figure out how to count with Slick (3).
val posts = for {
p <- Posts.query if p.receiver === userId
comments <- Comments.query if comments.postId === p.id
author <- Users.query if p.author === author.id
receiver <- Users.query if p.receiver === receiver.id
} yield (p, comments, author, receiver)
With the following relationships
Posts : Author : Receiver : Comments
1 : 1 : 1 : N
The result should be:
Future[Seq[(Post, User, User, Int)]]
with the int being the count of comments grouped by Posts
Any tips?
You need to group your result by post, author and receiver and the map to aggregate the comments by just counting them.
val posts = (for {
p <- Posts.query if p.receiver === userId
comment <- Comments.query if comments.postId === p.id
author <- Users.query if p.author === author.id
receiver <- Users.query if p.receiver === receiver.id
} yield (p, comment, author, receiver)) //So far thats your current query
.groupBy({ //Group by post, author and receiver
case (post, comment, author, receiver) =>
(post, author, receiver)
})
.map({ //Aggregate your comments (second argument in tuple) and count them
case ((post, author, receiver), list) => {
(post, author, receiver, list.map(_._2).count))
}
})
On mobile currently so this probably won't compile, but you should get the idea.
Related
I have these tables
object BooksAuthors : Table(name = "books_authors") {
val book = reference("book_id", Books, onDelete = ReferenceOption.CASCADE)
val author = reference("author_id", Authors, onDelete = ReferenceOption.CASCADE)
}
object Books : IntIdTable() {
val title = varchar("title", 250)
val isbn = varchar("isbn", 13)
}
object Authors : IntIdTable() {
val email = varchar("email", 100).uniqueIndex()
}
i would like to write a query that returns all books that dont have a specific author so i wrote this
suspend fun getBooksWithoutAuthorId(authorId: Int): List<BookDTO> = DbFactory.dbQuery {
val query = BooksAuthors.innerJoin(Books).select { BooksAuthors.author neq authorId }
Book.wrapRows(query).map { it.toDTO() }
}
But the query returns books that have the author. What am i doing wrong?
As Sebastian Redl mentioned, there could be books with multiple authors and your query doesn't cover that case.
Correct Exposed query should be:
val query = Books.select {
Books.id notInSubQuery
BooksAuthors.slice(BooksAuthors.book).select { BooksAuthors.author eq authorId }
}
It looks like you have an n:m mapping where a book can have multiple authors.
Your query, as written, finds any book that an author other than your selected one has authored.
This means if authors Alice and Bob wrote a book together, and you want to find books "not by Bob", you would still find the book because Alice took part.
Your desired query cannot be expressed as a simple join; you need nested queries instead.
Something like equivalent to this SQL:
SELECT * from books b WHERE ? NOT IN (
SELECT ab.author_id FROM authors_books ab WHERE ab.book_id = b.id);
Though I'm afraid I don't know how to express this in Exposed.
https://www.db-fiddle.com/f/7BsVUW95g6L4rXDBCoaXK3/0
I was trying to convert a query from SQL into Scala code with Slick, but I have got a compiler error in filter clause: constructor cannot be instantiated to expected type.
My code in Slick:
val subquery = (for {
pit <- PassInTripTable.table
t <- TripTable.table if pit.tripNoFk === t.tripNo
} yield (pit, t))
.map{ case (pit, t) => ( pit, Case.If(t.townFrom <= t.townTo).Then(t.townFrom ++ t.townTo).Else(t.townFrom ++ t.townTo) )}
.groupBy(_._1.idPsgFk)
.filter{ case ((pit, count), group) => ( group.map(_._2).countDistinct === 1)}
.map(_._1)
val query = PassengerTable.table.filter(_.idPsg in subquery).map(_.name)
db.run(query.result)
The query in SQL itself:
select name from passenger
where id_psg in
(
select id_psg from trip t,pass_in_trip pit
where t.trip_no=pit.trip_no
group by id_psg
having count(distinct case when town_from<=town_to then town_from+town_to else town_to+town_from end)=1
)
I would be very grateful if someone helped me to find an error.
From looking at your code, it looks like the type you are matching on is not supposed to be "((pit, count), group)".
groupBy in Slick only returns a collection of Tuple2s.
http://slick.lightbend.com/doc/3.0.0/queries.html
So, the filter might look something like...
.filter{ case (pit, count) => ( count.map(_._2).countDistinct === 1)}
The problem is that Slick .groupBy requires a .map call with aggregating functions afterwards. You can find detailed information here.
So, try this:
.groupBy(_._1.idPsgFk)
.map{ case (key, group) => (key, group.map(_._2).countDistinct)}
.filter{ case (_, count) => count === 1}
.map(_._1)
P.S.
I've also found "bad smells" in your code. You get pairs as a result of for-comrehension, but it looks like standard join would be more appropriate here (and more efficient), something like:
PassInTripTable.table.join(TripTable.table).on(_.tripNoFk === _.tripNo)
.map{ case (pit, t) => ...}
And why would you use such condition:
Case.If(t.townFrom <= t.townTo).Then(t.townFrom ++ t.townTo).Else(t.townFrom ++ t.townTo)? Its branches are the same, so equals to t.townFrom ++ t.townTo.
I have a model structure as following:
Group -> Many Parties -> Many Participants
In on of the API calls I need to get single groups with parties and it's participants attached.
This whole structure is built on 4 tables:
group
party
party_participant
participant
Naturally, with SQL it's a pretty straight forward join that combines all of them. And this is exactly what I am trying to do with slick.
Mu method is dao class looks something like this:
def findOneByKeyAndAccountIdWithPartiesAndParticipants(key: UUID, accountId: Int): Future[Option[JourneyGroup]] = {
val joins = JourneyGroups.groups join
Parties.parties on (_.id === _.journeyGroupId) joinLeft
PartiesParticipants.relations on (_._2.id === _.partyId) joinLeft
Participants.participants on (_._2.map(_.participantId) === _.id)
val query = joins.filter(_._1._1._1.accountId === accountId).filter(_._1._1._1.key === key)
val q = for {
(((journeyGroup, party), partyParticipant), participant) <- query
} yield (journeyGroup, party, participant)
val result = db.run(q.result)
result ????
}
The problem here, is that the result is type of Future[Seq[(JourneyGroup, Party, Participant)]]
However, what I really need is Future[Option[JourneyGroup]]
Note: case classes of JourneyGroup and Party have sequences for there children defined:
case class Party(id: Option[Int] = None,
partyType: Parties.Type.Value,
journeyGroupId: Int,
accountId: Int,
participants: Seq[Participant] = Seq.empty[Participant])
and
case class JourneyGroup(id: Option[Int] = None,
key: UUID,
name: String,
data: Option[JsValue],
accountId: Int,
parties: Seq[Party] = Seq.empty[Party])
So they both can hold the descendants.
What is the correct way to convert to the result I need? Or am I completely in a wrong direction?
Also, is this statement is correct:
Participants.participants on (_._2.map(_.participantId) === _.id) ?
I ended up doing something like this:
journeyGroupDao.findOneByKeyAndAccountIdWithPartiesAndParticipants(key, account.id.get) map { data =>
val groupedByJourneyGroup = data.groupBy(_._1)
groupedByJourneyGroup.map { case (group, rows) =>
val parties = rows.map(_._2).distinct map { party =>
val participants = rows.filter(r => r._2.id == party.id).flatMap(_._3)
party.copy(participants = participants)
}
group.copy(parties = parties)
}.headOption
}
where DAO method's signature is:
def findOneByKeyAndAccountIdWithPartiesAndParticipants(key: UUID, accountId: Int): Future[Seq[(JourneyGroup, Party, Option[Participant])]]
Given tables of:
case class Person(id: Int, name: String)
case class Dead(personId: Int)
and populated with:
Person(1, "George")
Person(2, "Barack")
Dead(1)
is it possible to have a single query that would produce a list of (Person, Option[Dead]) like so?
(Person(1, "George"), Some(Dead(1)))
(Person(2, "Barack"), None)
For slick 3.0 it should be something like this:
val query = for {
(p, d) <- persons joinLeft deads on (_.id === _.personId)
} yield (p, d)
val results: Future[Seq[(Person, Option[Dead])]] = db.run(query.result)
In slick, outer joins are automatically wrapped in an Option type. You can read more about joining here: http://slick.typesafe.com/doc/3.0.0/queries.html#joining-and-zipping
I'm trying to create a criteria query that grabs "RejectedRecords" by useruploaded that are not flagged as being deleted or the Facility in the RejectedRecord is in a list of facilities that a user is assigned to (user.UserFacilities). I have the first part working fine (By User and Not Deleted) but I'm not sure how to add the OR clause to take records that are in the collection of user-facilities. In SQL it would look like:
SELECT *
FROM RejectedRecords
WHERE (UserUploaded = 1 AND IsDeleted = 0)
OR FacilityId IN (SELECT FacilityId FROM UserFacility WHERE UserId = 1)
Here's my attempt in C# (Not sure how to perform the subquery):
public IList<RejectedRecord> GetRejectedRecordsByUser(User u)
{
return base._session.CreateCriteria(typeof(RejectedRecord))
.Add(
(
Expression.Eq(RejectedRecord.MappingNames.UserUploaded, u)
&& Expression.Eq(RejectedRecord.MappingNames.IsDeleted, false)
)
)
.List<RejectedRecord>();
}
The Key is to use Disjunction and Conjunction combined with a Subquery.
var facilityIdQuery = DetachedCriteria.For<UserFacility>()
.Add(Expression.Eq("User.Id", u))
.SetProjection(Projections.Property("Facility.Id"));
var results = session.CreateCriteria<RejectedRecords>()
.Add(
Restrictions.Disjunction()
.Add(
Restrictions.And(
Restrictions.Eq(RejectedRecord.MappingNames.UserUploaded, u),
Restrictions.Eq(RejectedRecord.MappingNames.IsDeleted, false)
)
)
.Add(Subqueries.PropertyIn("FacilityId",facilityIdQuery))
).List();