Using a Property Value Inside of createCriteria - mysql

I have 2 domain classes, Item and Manufacturer. The Manufacturer has a property on it to warn the user when the Item is about to expire (the date of which is stored as a Joda DateTime object). The relevant fields are set up like so.
class Manufacturer {
Integer expirationWarning
static hasMany = [items: Item]
}
and
class Item {
DateTime expirationDate
static belongsTo = [manufacturer: Manufacturer]
}
I'm trying to create a list of Items where the expiration is some time between now and now plus expirationWarning days. I have gotten the query working correctly in the MySQL terminal.
SELECT i.id FROM items as i
LEFT JOIN (manufacturer as m) ON (m.id = i.manufacturer_id)
WHERE i.expiration_date <= DATE_ADD(current_date, INTERVAL m.expiration_warning DAY);
Now I just need to execute the same query in Grails. I know that the createCriteria will look something like this:
def itemsExpiringSoon = Item.createCriteria().list(max: listMax, offset: params.offset) {
createAlias('manufacturer', 'm', CriteriaSpecification.LEFT_JOIN)
le('expirationDate', new DateTime().plusDays('m.expirationWarning'))
order('expirationDate', 'desc')
}
But I can't figure out what to put in place of 'm.expirationWarning' in order to use the value of that field in the DateTime.plusDays(). Any guidance on this would be incredibly helpful.

You cannot mix your query in this way, but the criteria have an option to add sql directly, then you can use your date_add().
def itemsExpiringSoon = Item.createCriteria().list(max: listMax, offset: params.offset) {
createAlias('manufacturer', 'm', CriteriaSpecification.LEFT_JOIN)
sqlRestriction('m.expiration_warning <= DATE_ADD(current_date, INTERVAL m.expiration_warning DAY)')
order('expirationDate', 'desc')
}
Note that in sqlRestriction() you write something that will be added in the final SQL, so we use the name of the database column, and not the attribute of the domain class.

Two and a half years later, I finally figured out a solution to this problem.
I created what is, in effect, a transient property that I applied GORM formulas to, that represents the current date plus the manufacturer specified expiration warning.
class Item {
Date expirationDate
Date pendingExpiration
static belongsTo = [manufacturer: Manufacturer]
static mapping = {
pendingExpiration formula: "(SELECT ADDDATE(current_date, mfr.expiration_warning) FROM manufacurer mfr WHERE mfr.id = manufacturer_id LIMIT 1)"
}
}
Now I can query against this property, and it will update dynamically as Manufacturer.expirationWarning does.

Related

filling a select field with model with certain tag - laravel

I have no problem with selecting a model tagged with certain tag
$lis = Capacitytype::find(124)->entities()->orderBy('name','asc')->get();
In my Capacitytype model I have this relation:
public function entities()
{
return $this->belongsToMany('App\Models\Entity', 'entity_capacitytypes', 'capacitytype_id', 'entity_id');
}
MY PROBLEM:
I wish to use the same selection f models to fill a select field in a form. (I use select2.js)
When I try to implement this code
$lis = array(null => 'Commitee') + Capacitytype::find(124)->entities()->orderBy('name','asc')->lists('name', 'id')->all();
I get this error:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in field list is ambiguous (SQL: select name, id from entities inner join entity_capacitytypes on entities.id = entity_capacitytypes.entity_id where entities.deleted_at is null and entity_capacitytypes.capacitytype_id = 6 order by name asc)
I tried to solve this issue by adding table name to my code, but this is as far as I was able to go:
->lists('entities.name', 'entities.id')
My Question:
how modify the code to get the desired collection for my select field?
Thank you.
You could make use of Laravel's with() to perform an eager loading as a workaround for this issue. To do so, just pass the name of the relation (i.e. entities) to the method:
Capacitytype::find(124)->with('entities')->orderBy('name','asc')->lists('entities.name', 'entities.id');
Or else, you could first fetch the records, and then reformat it to match your usecase:
$list = Capacitytype::find(124)->entities()->orderBy('name','asc')->get()->toArray();
As such, you can refine the returned list of arrays, to match your id => name format (hence, a substitute for the lists method).
$refinedList = array();
foreach($list as $entity){
$refinedList[$entity['id']] = $entity['name'];
}
And, there you go.

using mysql "order by case" in hibernate criteria

I'm using Hibernate 4.3.1 final, Mysql 5.5 and I want to use an "order by case" order logic on some joined entities.
A pure sql representation of what I wish to achieve would look something like:
select adv.id, adv.published_date
from advert as adv
join account as act on act.id = adv.act_id
join account_status as aas on aas.id = act.current_aas_id
order by case aas.status
when 'pending' THEN 1
when 'approved' THEN 1
else 2
end, adv.published_date;
This orders the adverts of pending and approved accounts before those of deactive accounts.
I've managed to do all the select query using hibernate criteria, but I'm not sure how to specify the order by case statement using that api.
I found this post:
http://blog.tremend.ro/2008/06/10/how-to-order-by-a-custom-sql-formulaexpression-when-using-hibernate-criteria-api/
but I need to reference joined entity classes in the order by case and I'm not sure how to do that.
Any help or suggestions much appreciated.
I think I found a solution.
In the end I created my own subclass of Order and overrode the public String toSqlString(Criteria,CriteriaQuery) method:
#Override
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
final String[] columns = criteriaQuery.getColumnsUsingProjection( criteria, getPropertyName() );
final StringBuilder fragment = new StringBuilder();
fragment.append(" case ").append(columns[0]);
fragment.append(" when 'pending' then 1 ");
fragment.append(" when 'approved' then 1 ");
fragment.append(" else 2 end");
return fragment.toString();
}
the important call (which I found in the original implementation of the order class) is the
criteriaQuery.getColumnsUsingProjection(criteria, getPropertyName());
Which gave me access to the alias for the column I wanted to order on using the case statement.
If anyone else is looking at this, if you are ordering on a property that is not on the root object, then make sure that you use aliases in your criteria joins and that you then reference those aliases correctly in the your custom Order propertyName when you instantiate it.
In addition to https://stackoverflow.com/a/24077763/258559
It is probably worth mention that now you're adding Order subclass like this
criteria.addOrder(new CustomOrder());
and not
criteria.addOrder(CustomOrder.desc(fieldName));
because desc/asc are the static methods which would still create an instance of Order instead of your CustomOrder

Error undefined local variable or method `created_at'

I write query to find out how much the user has reduced calories in a week, but I have this error.
How to avoid mistakes?
def self.calories_burned(current_user)
week = ((created_at - current_user.first_program_started_at.utc.beginning_of_day) / 86400 / 7).ceil.to_i || 1
find_by_sql("
SELECT
count(*) as cnt,
WEEK(#{week}) as week_number
FROM
user_daily_updates
WHERE
user_id=#{current_user.id}
GROUP BY
week_number
")
end
When you write this:
def self.calories_burned(current_user)
etc...
end
it's a method that can only be called like this:
MyUserModel.calories_burned(some_user)
In this case you are running the code on the class before it is instantiated, this means that the model hasn't even attached itself to a connection to the database and because of that you will not be able to access attributes that pertain to your model.
On the other hand, if you write this:
def calories_burned
etc...
end
You don't need to pass the user to the method, you simply call it on the controller after instantiating your model, like this:
id = 123
current_user = MyUserModel.find(id)
current_user.calories_burned
where current_user.calories_burned will return the value you are looking for based on the current user.
After taking a closer look at your method
it should look more like this:
def calories_burned
week = ((created_at - first_program_started_at.utc.beginning_of_day) / 86400 / 7).ceil.to_i || 1
UserDailyUpdate.where(["user_id = ?", id]).where(["week = ?",week]]).count(:all, :group => 'week_number')
end
If I understood correctly what you were trying to do with your query, this should give you the same result. Now, I should mention I am assuming that when you created the table user_daily_updates, you also created a model UserDailyUpdate.
You should take a look at ActiveRecord's documentation for searching using conditions (all of section 2) in order to have a better understanding of what I just did.
Apneadiving is right, but I would also look to refactor your code:
def self.calories_burned(current_user)
week = ((##created_at## - current_user.first_program_started_at.utc.beginning_of_day) / 86400 / 7).ceil.to_i || 1
select("#{self.table_name}.*, count(*) as cnt, WEEK(#{week}) as week_number").where(user_id: current_user.id).group(:week_number)
end
You'll need to pull created_at from the db, as it won't be present unless you have an instance of a class already loaded

How to take Date from sql db as a single result and compare with current date

i have a complex problem with Date field. Describe what i want to do:
I have field date1 as Date in my db.
#Temporal(TemporalType.DATE)
private Date date1;
I want to take data from this field and compare with current date.
#Query("SELECT date1 FROM Table io WHERE io.date1 >= DATE_FORMAT(CURRENT_DATE, '%Y-%m-%e')")
Date findAll2();
public boolean CheckDate1(){
currentDate = new Date();
date1 = getInterimOrdersRepo().findAll2();
if(currentDate.before(date1) || currentDate.equals(date1)){
System.out.println("TRUE");
System.out.println("currentDate = "+currentDate);
return true;
}
else{
System.out.println("FALSE");
return false;
}
}
but i have an error:
result returns more than one elements; nested exception is javax.persistence.NonUniqueResultException
When method return false i want do Update field data1 with " " empty data.
I using jsf, what i must to do?
It seems that you are trying to read several values from the table into a single variable, and that is the error.
findall2 returns an array (most likely) and u should read one of it's values - try reading first one.
Furthermore, I believe that you can skip the "DATE_FORMAT" in your query, and this is a very strange way to write a code. Not clear what u are trying to achieve here

how to save object with sysdate functionality

i have an object student. then there is a property called expiry date. this is need to be set with the database sysdate + a value(1000).
so how can i save with jpa. can't i do it on the jpa prepared statement query itself?
if i use sql.date is it exactly give the same value as when we are saving as 'sysdate'?
can't i do it with on the query itself?
other properties can be set to the object. but the problem is this expiry date as it needs the sysdate and add another value to it eg: expiry date = sysdate + 1000; how can i do it with jpa prepared statements. please reply me
What about use a seperate query to retrieve sysdate and set it to your object.
I usally create a Clock to handle this:
public interface Clock {
Date now();
}
public class HibernateClock implements Clock {
//use query to retieve the db sysdate
}
You can add it in java itself. Use calendar object to add days.
Calendar expirydate=Calendar.getInstance();
expirydate.add(Calendar.DATE, 1000);
then
expirydate.getTime() will give you expire date object.
Why do you want to use sysdate? Its syntax is database specific and also dependent on the DB-hosting machine's clock, rather than on your application-hosting machine's clock.
Easiest way is to use java.util.Date as the expiryDate's type and the value of new Date(System.currentTimeInMillis() + 1000). Use this value in the field's declaration for featuring it as default value on new Student creation or use it as the value passed to the setter when modifying an existant Student.
public class Student {
...
/**
* Using java.util.Date here. Hibernate knows to convert it automagically to java.sql.Date.
* Set default value to current time + 1 second, if this is your requirement.
*/
private Date expiryDate = new Date(System.currentTimeInMillis() + 1000);
public void setExpiryDate(final Date expiryDate) {
this.expiryDate = expiryDate;
}
...
}