Вложенные SQL-запросы Rails

У меня есть модель базы данных Position(lat,lon), которая содержит latitudes и longitudes.

У меня есть действие с контроллером show_close_by, которое получает позицию в градусах (my_lat, my_lon), допустимость (в километрах) и должна возвращать список позиций в базе данных, находящихся в пределах допустимого диапазона.

Для этого я использую формулу haversine_distance, которая вычисляет расстояние в километрах (на поверхности Земли) между двумя координатами (lat1, lon1, lat2, lon2).

Чтобы сделать запрос быстрее, я написал целую формулу haversine_distance в запросе:

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

Специфика запроса не имеет значения. Я сомневаюсь: необходимо ли вычислить эту огромную функцию для КАЖДОЙ позиции в базе данных? Могу ли я отфильтровать некоторые позиции, которые явно слишком далеки, с более простой функцией?

Ну, я могу: с вложенным SQL-запросом я могу запросить базу данных для позиций, которые находятся в большом "квадрате" (в пространстве lat/lon), а затем отфильтровывать те, у которых более затратная тригонометрическая функция. Что-то вроде следующего:

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

Наконец, мой вопрос: как я могу реализовать это в Rails (без написания всего запроса сам)? Выполняет ли Positions.where(reasonable_upper_bound).where(costly_but_accurate_restriction) вложенный запрос? Если нет, то как?

Спасибо большое!

Ответы

Ответ 1

Здесь, как делать вложенные запросы:

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

Он выполняет следующий запрос:

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

Обратите внимание, что только id будет извлечено из таблицы products. Если вы пытаетесь сделать еще один способ подключения двух объектов, и эта магия не подходит, используйте Product.select(:some_field).where(...).

Ответ 2

Я хотел бы предложить обновленный ответ. В Rails v5.0.x вы можете использовать select чтобы использовать атрибут, отличный от id, все еще выполняя вложенный запрос.

LineItem.where(product_id: Order.where(price: 50).select(:product_id))