2012-04-13 15 views
15

I tienen un modelo de base de datos Position(lat,lon) que mantiene latitudes y longitudes.Rieles anidadas consultas SQL

tengo una acción de un controlador llamado show_close_by que recieves una posición en grados (my_lat, my_lon), una tolerancia (en kilómetros) y debe devolver la lista de posiciones en la base de datos que están dentro del rango de tolerancia.

Para eso, uso la fórmula haversine_distance que calcula la distancia en kilómetros (en la superficie de la Tierra) entre dos coordenadas (lat1, lon1, lat2, lon2).

Para hacer la consulta más rápido, escribí todo el haversine_distance fórmula en la consulta:

... 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 

Los detalles de la consulta no importan. Mi duda es: ¿es necesario calcular esta gran función para CADA puesto en la base de datos? ¿Puedo filtrar algunas posiciones que están claramente demasiado lejos con una función más simple?

Bueno, puedo: con una consulta SQL anidada, puedo consultar la base de datos para las posiciones que están dentro de un gran "cuadrado" (en lat/lon), y luego filtrar aquellas con la función trigonométrica más costosa. Algo así como lo siguiente:

SELECT * FROM (SELECT * FROM Positions WHERE lat-latitude < some_reasonable_upper_bound AND lon-longitude < same_upper_bound) WHERE costly_haversine_distance < tolerance 

Por último, mi pregunta: ¿Cómo puedo aplicar esto en rieles (sin tener que escribir toda la consulta a mí mismo)? ¿Hace Positions.where(reasonable_upper_bound).where(costly_but_accurate_restriction) una consulta anidada? Si no, ¿cómo?

¡Muchas gracias!

+0

¿Sabes acerca de [Geocoder] (https://github.com/alexreisner/geocoder)? Instalas la gema, agregas una línea en tu modelo de posición y luego puedes hacer cosas como 'Position.near ([40.71, 100.23], 20)'. Esta joya es bastante popular, así que diría que hacen lo que quieres hacer bastante bien. (Así que ya sabes, vincular las cláusulas 'where' no hace lo que quieres. Creo que simplemente anula las anteriores). – Robin

+0

Sí, sé sobre Geocoder y podría terminar usándolo, pero me pareció un poco grande para lo que quiero (solo quiero distancias entre objetos). ¿Cuánto más lenta es una aplicación que carga un objeto Geocoder completo en comparación con la función barebones haversine_distance que programé? – gdiazc

+0

@Robin No anula el anterior, pero * no * anexa las condiciones con 'Y' a la consulta actual, que no es exactamente lo que el asker está buscando. – nzifnab

Respuesta

25

Aquí es cómo hacer consultas anidadas:

LineItem.where(product_id: Product.where(price: 50)) 

Se hace la petición siguiente:

SELECT "line_items".* FROM "line_items" 
WHERE "line_items"."product_id" IN 
(SELECT "products"."id" FROM "products" WHERE "products"."price" = 50) 

Tenga en cuenta que sólo id s se va a recoger la mesa products. Si intenta hacer otra forma de conectar dos entidades y esta magia no es adecuada, use Product.select(:some_field).where(...).

+0

Gracias por la publicación, pero necesito poca ayuda. Cómo escribir la consulta si necesito seleccionar todos los mensajes comunicados entre dos usuarios (digamos id1 e id2) y el mensaje tiene sender_id y receiver_id. Entonces necesito seleccionar "todos los mensajes cuyo ((sender_id = id1 y reciver_id = id2) o (sender_id = id2 y reciver_id = id1))" en rieles. Necesito ordenarlos y paginarlos, lo cual no se puede hacer si hago dos consultas por separado y luego las agrego (tendré el resultado deseado, pero la paginación se vuelve difícil). Así que por favor ayúdenme a escribir una consulta de línea única para esto. Cualquier ayuda será apreciada. – zeal

+0

@zeal Pruebe https: // github.com/activerecord-hackery/squeel para consultas complicadas. – jdoe