| Grails Version: 3.0.9
| Groovy Version: 2.4.5
| JVM Version: 1.8.0_60
Hi,
I have the following GORM query involving a join between the 'Event' and 'EventCategory' domain objects with page results.
def advancedSearchWithPagedResults(int max, int offset, String search, Date startDate, Date endDate, List myEventCategories) {
// need to convert our list of ints to longs as the field they'll be compared to is a long
def listOfLongs = myEventCategories.collect {
it.toLong()
}
def wildcardSearch = search ? '%' + search + '%' : '%'
def ids = Event.createCriteria().list {
projections {
distinct 'id'
}
maxResults max
firstResult offset
or {
like("eventName", wildcardSearch)
like("address.town", wildcardSearch)
like("address.county", wildcardSearch)
}
and {
if (listOfLongs) {
eventCategories {
'in'("id", listOfLongs)
}
}
between("startDateTime", startDate, endDate)
eq("enabled", true)
}
order("startDateTime", "asc")
}
/* Get the acual events using the list of id's just obtained */
def results = Event.getAll(ids)
return results
}
However, I need to add in / merge the following MySQL query that calculates the distance of each event (in miles) from the supplied latitute and longitude (51.519159, -0.133190) and then filters out any event that is in excess of 25 miles (in the example). The events are also ordered by distance from the supplied lat/long.
SELECT
`event`.*,
( 3959 * acos( cos( radians(51.519159) ) * cos( radians( `event`.address_latitude ) )
* cos( radians(`event`.address_longitude) - radians(-0.133190)) + sin(radians(51.519159))
* sin( radians(`event`.address_latitude)))) AS distance
FROM `event`
WHERE `event`.enabled = 1
HAVING distance < 25
ORDER BY distance;
My question is how to best approach changing the GORM query to incorporate the distance calulations?
Do I need to throw out my GORM query and resort to a native HQL query? I'm hoping not.
Any thoughts would be greatly appreciated.
I'll include relevant parts of the two domain objects for completeness.
class Event implements Taggable {
static hasMany = [courses: Course,
eventCategories: EventCategory,
faqs: FAQ]
static belongsTo = [eventOrganiser: EventOrganiser]
java.util.Date dateCreated
java.util.Date lastUpdated
boolean enabled = true
String eventName
String organisersDescription
#BindingFormat('dd/MM/yyyy HH:mm')
java.util.Date startDateTime
#BindingFormat('dd/MM/yyyy HH:mm')
java.util.Date endDateTime
#BindingFormat('dd/MM/yyyy HH:mm')
java.util.Date entriesOpenDateTime
#BindingFormat('dd/MM/yyyy HH:mm')
java.util.Date entriesCloseDateTime
BigDecimal fromPrice
Address address
Contact primaryContact
static embedded = ['address','primaryContact']
// transient fields
double distanceFromUsersPostcode
....
}
class EventCategory {
static hasMany = [events:Event]
static belongsTo = [Event]
String parentCategoryName
String parentSubCategoryName
String categoryName
String description
int order
}
You can store the distance query internally within a domain class as a formula and then call upon it as if it were a property of that class.
Event class:
static mapping = {
distance formula: """
(3959 * acos(cos(radians(51.519159)) *
cos(radians(ADDRESS_LATITUDE)) * cos( radians(ADDRESS_LONGITUDE) -
radians(-0.133190)) + sin(radians(51.519159)) * sin( radians(ADDRESS_LATITUDE))))
"""
}
You may need to experiment with the formula's string (whether the new line character will cause a problem).
It also seems like several parts of the query are constants and could be factored out: acos(cos(radians(51.519159)), radians(-0.133190), sin(radians(51.519159))
Criteria Builder
You can use the distance property now like any other property:
lt('distance', 25)
Related
I have a column defined as follows:
#Type(type = "json")
#Column(name = "ex_data")
#JsonProperty
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
private T exData;
Where, given type T, I can store a variety of basic JSON objects in my table, like so:
{
"active": true,
"color": "red",
"flavor": "cherry"
}
If I want to created a sorted, paginated, query against the table, is it possible to use the content of the JSON for the Sort object?
i.e. an equivalent to this, which presently does not work:
Sort sort = Sort.by("exData.color").ascending();
JSON is not directly supported by JPA.
One way would be to use a native SQL function to access a specific property nested inside the json column.
Unfortunately, native expressions are not supported when using Sort, which only accepts entity properties to order by.
Solution 1: native query with json key parameter and pagination
#Repository
public interface JsonSortNativeRepositoryMySql extends JpaRepository<JsonSortNativeEntity, Long> {
#Query(value = "SELECT * FROM json_sort_native_entity ORDER BY "
// flexibly extract the sort value from json:
+ "my_json_column->> :sortKey" //
+ " DESC", //
countQuery = "select count(*) from json_sort_native_entity", //
nativeQuery = true)
Page<JsonSortNativeEntity> findSortedNative(Pageable pageable, #Param("sortKey") String sortKey);
}
The corresponding pageable is created without Sort parameter, and the sort key can be dynamically supplied when calling the native query method:
var pageable = PageRequest.of(0, 20); // first page with 20 entries
var pageResult = repository.findSortedNative(pageable, "$.color");
Solution 2: sort by computed attribute value
Another option would be to specify a computed attribute value using a Hibernate #Formula as part of the Entity:
#Formula("my_json_column->>'$.color'") // <-- fixed key "color" to extract from json column
private String formulaValue;
This way, a normal Sort can be used:
var pageable = PageRequest.of(0, 20, Sort.by("formulaValue").descending());
var pageResult = repository.findAll(pageable); // standard JpaRepository method
However, the key to extract the sort values is fixed in this case (possible alternative: Spring Data - Page Request - order by function)
MySQL JSON reference: https://dev.mysql.com/doc/refman/8.0/en/json.html
I have a SQL query in a Rails model that responds with an array of objects. And each object has an attribute called points with a value.
But its preferable for query to just return an array of points like [10,15,5] instead of [object,object,object] which requires then extracting the points out into another array to be useful.
Model file
LAST_3_SELECT = "
SELECT
(
(data.ap / (data.apa * 1.0))
+
(data.vp / (data.vpa * 1.0))
)
/ 2 * 1.5 * data.level
AS points
FROM data
WHERE data.user_id = ?
GROUP BY data.id
ORDER BY data.created_at DESC
LIMIT 3
"
def self.last_3(user_id)
connection.select_all(sanitize_sql_array( [LAST_3_SELECT, user_id]), "last-3")
end
Is this possible to do in a query itself, or necessary to do in a method outside it?
I don't have much experience writing raw SQL queries into Rails methods so any guidance would be very appreciated.
You can use pluck to get the points into an array
def self.last_3(user_id)
connection.select_all(sanitize_sql_array( [LAST_3_SELECT, user_id]), "last-3").pluck(:points)
end
Here points is the column name to be plucked
I need to show location in Google Map.
I am using Symfony2, doctrine and MySQL.
Need help in what is the right type in yml/doctrine.
Thanks in Advance.
From the Doctrine Docs:
decimal
Maps and converts numeric data with fixed-point precision. If you need an exact precision for numbers with fractions, you should consider using this type.
As a result I use decimal for Latitude and Longitude in Doctrine. I can confirm that Decimal with a precision of 20 and a scale of 16 works.
/**
* #var string
*
* #ORM\Column(name="latitude", type="decimal", precision=20, scale=16)
*/
private $latitude;
/**
* #var string
*
* #ORM\Column(name="longitude", type="decimal", precision=20, scale=16)
*/
private $longitude;
I would use an "object" data type and save an own-typed object with the location info in it.
You could create an object named "Location" which implements an interface that forces it to have a latitude and longitude, then use:
$location = new \Location();
$location->setLatitude($lat);
$location->setLongitude($long);
$entity->setLocation($location);
Good luck!
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.
Title pretty much says it. I have two JSON objects and I want to know if they are equal (have all the same property values).
I could stringify them both, but I'm not sure if two equal objects will always produce the same output:
E.g:
{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"favoriteColors": ["blue", "green", "red"]
}
Is a different string from:
{
"age": 25,
"lastName": "Smith",
"firstName": "John",
"favoriteColors": ["blue", "green", "red"]
}
But as objects they have identical properties.
There is a method for this in the Flex SDK. It is in the class ObjectUtil. Here is the description from the source :
/**
* Compares the Objects and returns an integer value
* indicating if the first item is less than greater than or equal to
* the second item.
* This method will recursively compare properties on nested objects and
* will return as soon as a non-zero result is found.
* By default this method will recurse to the deepest level of any property.
* To change the depth for comparison specify a non-negative value for
* the depth parameter.
* #param a Object.
* #param b Object.
* #param depth Indicates how many levels should be
* recursed when performing the comparison.
* Set this value to 0 for a shallow comparison of only the primitive
* representation of each property.
* For example:
* var a:Object = {name:"Bob", info:[1,2,3]};
* var b:Object = {name:"Alice", info:[5,6,7]};
* var c:int = ObjectUtil.compare(a, b, 0);In the above example the complex properties of a and
* b will be flattened by a call to toString()
* when doing the comparison.
* In this case the info property will be turned into a string
* when performing the comparison.
* #return Return 0 if a and b are null, NaN, or equal.
* Return 1 if a is null or greater than b.
* Return -1 if b is null or greater than a.
* #langversion 3.0
* #playerversion Flash 9
* #playerversion AIR 1.1
* #productversion Flex 3
*/
public static function compare (a:Object, b:Object, depth:int=-1) : int;
If you don't want the whole SDK maybe you can just get this function/class and use that source.
You can see the source here. Most of the work is done in the function internalCompare.
Edit: Barış' answer is the best option, as it's tried and tested. Just in case this comes in handy for someone though:
Given that JSON values are limited to a small set of simple types, it should be possible to recurse through the properties fairly easily. Something along these lines works with your example:
private function areEqual(a:Object, b:Object):Boolean {
if (a === null || a is Number || a is Boolean || a is String) {
// Compare primitive values.
return a === b;
} else {
var p:*;
for (p in a) {
// Check if a and b have different values for p.
if (!areEqual(a[p], b[p])) {
return false;
}
}
for (p in b) {
// Check if b has a value which a does not.
if (!a[p]) {
return false;
}
}
return true;
}
return false;
}
Maybe you can convert the two objects to string then compare them
function compareJSON(a:Object, b:Object):Boolean
{
return JSON.stringify(a)===JSON.stringify(b);
}