I have a database model Position(lat,lon)
which holds latitudes
and longitudes.
I have a controller action called show_close_by
which recieves a position in degrees (my_lat, my_lon)
, a tolerance (in kilometers) and should return the list of positions in the database that are within tolerance range.
For that, I use the haversine_distance formula which calculates the distance in kilometers (on the surface of the Earth) between two coordinates (lat1, lon1, lat2, lon2)
.
To make the query faster, I wrote the whole haversine_distance
formula in the query:
... WHERE 2*6371*asin(sqrt( power( sin( (:lat2-latitude)*pi()/(180*2) ) ,2) + cos(latitude*pi()/180)*cos(:lat2*pi()/180)*power(sin( (:lon2-longitude)*pi()/(180*2) ),2) )) < tolerance
The specifics of the query don’t matter. My doubt is: is it necessary to calculate this huge function for EVERY position in the database? Can I filter out some positions that are clearly too far away with a simpler function?
Well, I can: With a nested SQL query, I can query the database for positions that are within a large “square” (in lat/lon space), and then filter those with the more costly trigonometric function. Something like the following:
SELECT * FROM ( SELECT * FROM Positions WHERE lat-latitude < some_reasonable_upper_bound AND lon-longitude < same_upper_bound ) WHERE costly_haversine_distance < tolerance
Finally, my question: how can I implement this in Rails (without writing the whole query myself)? Does Positions.where(reasonable_upper_bound).where(costly_but_accurate_restriction)
make a nested query? If not, how?
Thanks a lot!
Advertisement
Answer
Here’s how to make nested queries:
LineItem.where(product_id: Product.where(price: 50))
It makes the following request:
SELECT "line_items".* FROM "line_items" WHERE "line_items"."product_id" IN (SELECT "products"."id" FROM "products" WHERE "products"."price" = 50)
Note that only id
s will be fetched from the products
table. If you are trying to make another way to connect two entities and this magic isn’t suitable, use Product.select(:some_field).where(...)
.