Grails distinct projection get the result count of distinct items - grails

I am using grails-2.5.6 version. I am using spring-security-core plugin. I have a criteria query on UserRole table. Where I want to find all distinct users by a role. It is working properly.
But the problem is the pagination effect. When I am counting on the list it is counting on UserRole list object. But I need the count on distinct projection items. Here is my attempt below:
def list(Integer max) {
def userInstanceList = UserRole.createCriteria().list(params) {
createAlias('user', 'au')
createAlias('role', 'ar')
projections {
distinct("user")
}
if (params.roleId) {
eq('ar.id', params.getLong("roleId"))
}
}
def totalCount = userInstanceList.totalCount
[userInstanceList: userInstanceList, totalCount: totalCount]
}
Here, totalCount is the number of UserRole list. But I want the distinct projection count.

I would tackle this slightly differently, you want to analyse the users, not the userroles.
So I'd do something like:
List<User> usersWithRole = UserRole.createCriteria().list(params) {
role {
eq('id', params.getLong("roleId"))
}
}*.user
int count = usersWithRole.size()
Unless of course there's hundreds or thousands of users, in which case I wouldn't want to load all of them each time and would revert to SQL.
Is this a custom version of spring security you're using? I've never seen Roles with a 'long' based ID, usually, the key is a String representing the Authority name.

Usually the DBAs see the use of distinct keyword as a code-smell.
In your case I would rather use the User as the main domain object to run the query against and a group by clause:
long id = params.getLong "roleId"
def list = User.createCriteria().list( params ) {
projections{
groupProperty 'userRole.role.id'
}
if( id )
userRole{
role{
eq 'id', id
}
}
}

Related

How to get a list of distinct records with projections in grails?

Is there a way that I can get a list of distinct Order objects (based on customerName) with projections (selected fields only)?
Assuming only the id would be different, I want to fetch orders having unique customerName. Is it possible using projections or any other way?
My code is:
def criteria = Order.createCriteria()
def orders = criteria.list() {
and {
eq("showAddress", true)
like("customerName", "%abcdPqrs%")
}
projections {
distinct("customerName")
property("deliveryAddress")
property("billingAddress")
property("")
}
}
return orders
The above code fetches duplicate (customerName) records from Order, how can I fix this?
If you will see the SQL query generated by GORM, you will find that the distinct will apply on a complete row instead of the customerName. You can enable the logs by putting
logSql = true
in datasource.groovy.
You can try this
def criteria = Order.createCriteria()
def orders = criteria.list() {
and {
eq("showAddress", true)
like("customerName", "%abcdPqrs%")
}
projections {
groupProperty("customerName")
property("deliveryAddress")
property("billingAddress")
property("")
}
}

How can I write this relatively simple criteria query involving three domains?

Just for background, let us have these three domain classes:
class Group {
Long id
Person person
}
class Person {
Long id
Country country
String name
}
class Country {
Long id
}
So, with these classes in mind, I am given a Group object's id as well as a Country object's id. I would like to get the list of Person objects based on these two.
It seems relatively simple, but I am new to criteria queries and so I am struggling to figure out what I am doing wrong. This is what I have so far:
def c = Group.createCriteria()
def names = c.list (order: "asc") {
createAlias('person', 'p')
createAlias('p.country', 'c')
and {
eq ('c.id', Long.valueOf(countryId))
eq ('id', groupId)
}
projections {
property('p.name')
}
}
Of course, this is wrong as it is throwing errors. Can someone please let me know what I am doing wrong?
Thanks for your help!
static def searchPersons(Map params) {
return Group.createCriteria().list(params, {
if(params.groupId) {
eq('id', params.groupId.toLong())
}
person {
order('name')
if(params.countryId) {
country {
eq('id', params.countryId.toLong())
}
}
}
projections {
property('person')
}
})
}
Still, it might be better to add the necessary associations (hasMany, etc.) on your domains.
The first thing you can do is improve the associations between your domain classes. This will help make criteria queries simpler (and deter monkey-patching later).
In your example, the association between Person an Group is a one-to-many; one person can have many groups. That may be your intention, but it also means that a group can have only one person. Basically, there's no way to group people together.
I'm going to assume that you want a many-to-one relationship so that many Person (people) can be in the same Group. With this in mind, the domain classes (with the explicit IDs left in) would look like this:
class Group {
Long id
}
class Person {
Long id
Country country
String name
Group group
}
class Country {
Long id
}
As for the query, since your expected result is instances of Person, the best place to start is with Person rather than Group.
List of Person instances
Here's how to get a list of Person instances.
Where query
def people = Person.where {
country.id == countryId
group.id == groupId
}.list()
Criteria query
def people = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
}
List of Person names
Notice that there's a discrepancy between your question and example. You asked for a list of Person instances, yet your example demonstrates attempting to get a list of Person names .
Here's how to get a list of names of the Person instances.
Where query
def names = Person.where {
country.id == countryId
group.id == groupId
}.projections {
property 'name'
}.list()
Criteria query
def names = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
projections {
property 'name'
}
}

Grails - How to get list of distinct User Objects

Is there a way that, i can get list of distinct User objects(based on username). And still get result as a List of User Objects rather than, List of username's.
My code is
def criteria = User.createCriteria()
def users = criteria.list() {
projections {
distinct("username")
}
setResultTransformer(CriteriaSpecification.ROOT_ENTITY)
}
return users
Currently am getting List of the usernames, not User.
Ya projection is like filtering and selecting by username you should change it to
def criteria = User.createCriteria()
def users = criteria.listDistinct() {
projections {
groupProperty("username")
}
}
return users
JOB DONE!
One of these should work - I haven't tested any of them, I leave that up to you :)
User.list().unique()
User.list().unique() with the equals() method on the User domain class overridden to compare objects using the username
User.list().unique { it.username } (might need toArray() after list())
def criteria = User.createCriteria()
def users = criteria.list() {
projections {
distinct("username")
}
setResultTransformer(CriteriaSpecification.ROOT_ENTITY)
}
Just replace setResultTransformer(CriteriaSpecification.ROOT_ENTITY) with resultTransformer(ALIAS_TO_ENTITY_MAP). You will get a list of string as a result
otherwise just replace .list with .listDistinct and use do not need distinct("username"), just can be property("username");
Usually people get problems with pagination. not results. If you already had something like:
User.createCriteria().list([max:params.max,offset:params.offset],{
createAlias("others", "others", CriteriaSpecification.LEFT_JOIN);
ilike("others.firstName", "%${query}%");
});
It could result in row duplicates. Because .listDistinct() does not support pagination, just add
resultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
So query will look like this:
User.createCriteria().list([max:params.max,offset:params.offset],{
resultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
createAlias("others", "others", CriteriaSpecification.LEFT_JOIN);
ilike("others.firstName", "%${query}%");
});
Where ever you got a collection (list, array, ...) (I don't know if work with any type of collection, but work in all that i could test). Use unique{ it.property }:
Example:
def users = []
for (def room in rooms) {
users.addAll(room.users)
}
return users.unique{ it.id }
Using where query(Detached criteria):
def userListQuery = User.where{
// Your search criteria
}
def userList = userListQuery.list().unique{ it.username }
it should result one query for distinct results.
This will get you the distinct user object based on userName
def userInstance = User.list().unique{ it.user_name}

GORM createCriteria and list do not return the same results : what can I do?

I am using Nimble and Shiro for my security frameworks and I've just come accross a GORM bug. Indeed :
User.createCriteria().list {
maxResults 10
}
returns 10 users whereas User.list(max: 10) returns 9 users !
After further investigations, I found out that createCriteria returns twice the same user (admin) because admin has 2 roles!!! (I am not joking).
It appears that any user with more than 1 role will be returned twice in the createCriteria call and User.list will return max-1 instances (i.e 9 users instead of 10 users)
What workaround can I use in order to have 10 unique users returned ?
This is a very annoying because I have no way to use pagination correctly.
My domain classes are:
class UserBase {
String username
static belongsTo = [Role, Group]
static hasMany = [roles: Role, groups: Group]
static fetchMode = [roles: 'eager', groups: 'eager']
static mapping = {
roles cache: true,
cascade: 'none',
cache usage: 'read-write', include: 'all'
}
}
class User extends UserBase {
static mapping = {cache: 'read-write'}
}
class Role {
static hasMany = [users: UserBase, groups: Group]
static belongsTo = [Group]
static mapping = { cache usage: 'read-write', include: 'all'
users cache: true
groups cache: true
}
}
Less concise and clear, but using an HQL query seems a way to solve this problem. As described in the Grails documentation (executeQuery section) the paginate parameters can be added as extra parameters to executeQuery.
User.executeQuery("select distinct user from User user", [max: 2, offset: 2])
this way you can still use criteria and pass in list/pagination paramaters
User.createCriteria().listDistinct {
maxResults(params.max as int)
firstResult(params.offset as int)
order(params.order, "asc")
}
EDIT: Found a way to get both! Totally going to use it now
http://www.intelligrape.com/blog/tag/pagedresultlist/
If you call createCriteria().list() like this
def result=SampleDomain.createCriteria().list(max:params.max, offset:params.offset){
// multiple/complex restrictions
maxResults(params.max)
firstResult(params.offset)
} // Return type is PagedResultList
println result
println result.totalCount
You will have all the information you need in a nice PagedResultList format!
/EDIT
Unfortunately I do not know how to get a combination of full results AND max/offset pagination subset in the same call. (Anyone who can enlighten on that?)
I can, however, speak to one way I've used with success to get pagination working in general in grails.
def numResults = YourDomain.withCriteria() {
like(searchField, searchValue)
order(sort, order)
projections {
rowCount()
}
}
def resultList = YourDomain.withCriteria() {
like(searchField, searchValue)
order(sort, order)
maxResults max as int
firstResult offset as int
}
That's an example of something I'm using to get pagination up and running. As KoK said above, I'm still at a loss for a single atomic statement that gives both results. I realize that my answer is more or less the same as KoK now, sorry, but I think it's worth pointing out that rowCount() in projections is slightly more clear to read, and I don't have comment privileges yet :/
Lastly: This is the holy grail (no pun intended) of grails hibernate criteria usage references; bookmark it ;)
http://www.grails.org/doc/1.3.x/ref/Domain%20Classes/createCriteria.html
Both solutions offered here by Ruben and Aaron still don't "fully" work for pagination
because the returned object (from executeQuery() and listDistinct) is an ArrayList
(with up to max objects in it), and not PagedResultList with the totalCount property
populated as I would expect for "fully" support pagination.
Let's say the example is a little more complicated in that :
a. assume Role has an additional rolename attribute AND
b. we only want to return distinct User objects with Role.rolename containing a string "a"
(keeping in mind that a User might have multiple Roles with rolename containing a string "a")
To get this done with 2 queries I would have to do something like this :
// First get the *unique* ids of Users (as list returns duplicates by
// default) matching the Role.rolename containing a string "a" criteria
def idList = User.createCriteria().list {
roles {
ilike( "rolename", "%a%" )
}
projections {
distinct ( "id" )
}
}
if( idList ){
// Then get the PagedResultList for all of those unique ids
PagedResultList resultList =
User.createCriteria().list( offset:"5", max:"5" ){
or {
idList.each {
idEq( it )
}
}
order ("username", "asc")
}
}
This seems grossly inefficient.
Question : is there a way to accomplish both of the above with one GORM/HQL statement ?
You can use
User.createCriteria().listDistinct {
maxResults 10
}
Thanks for sharing your issue and Kok for answering it. I didn't have a chance to rewrite it to HQL. Here is my solution (workaround): http://ondrej-kvasnovsky.blogspot.com/2012/01/grails-listdistinct-and-pagination.html
Please tell me if that is useful (at least for someone).

Using groupProperty and countDistinct in Grails Criteria

I'm using Grails 1.2.4. I would like to know on how can I sort by "countDistinct" (descending) and with groupProperty inside a projections.
Here are my domains:
class Transaction {
static belongsTo = [ customer : Customer, product : Product ]
Date transactionDate = new Date()
static constraints = {
transactionDate(blank:false)
}
}
class Product {
String productCode
static constraints = {
productCode(blank:false)
}
}
In MySQL terms, this is what I want:
select
product_id,
count(product_id)
from
transaction
group by
product_id
order by
count(product_id) desc
In general term, I would like to get a list of products (or just product id) sorted by the number of transactions a product had (descending)
This is my guess:
def c = Transaction.createCriteria() def transactions = c.list {
projections {
groupProperty("product")
countDistinct("product")
}
maxResults(pageBlock)
firstResult(pageIndex) }
def products = transactions.collect { it[0] }
But it doesn't give my expected result. Any lead on this will be highly appreciated. Thanks!
Try this:
def c = Transaction.createCriteria()
def transactions = c.list {
projections {
groupProperty("product")
countDistinct("id")
}
maxResults(pageBlock)
firstResult(pageIndex)
}
Your criteria query is actually equivalent to:
select
product_id,
count(**distinct** product_id)
from
transaction
group by
product_id
order by
count(product_id) desc
Note the distinct. You want to count the number of distinct transactions per product, so counting the transaction id (or any transaction property) makes more sense. Product id just happens to work without the distinct clause.
You can turn on the super useful hibernate SQL logging for debugging this kind of issue. It will show you exactly how your criterias are being transformed into SQL. At runtime:
org.apache.log4j.Logger.getLogger("org.hibernate").setLevel(org.apache.log4j.Level.DEBUG)
or throw this in your Config.groovy:
environments {
development {
log4j = {
debug 'org.hibernate'
}
}
}
EDIT: use countDistinct("id", "transactionCount") as the projection and order("transactionCount") will sort by the count.

Resources