Запрос N ближайших записей базы данных с использованием интерфейса запроса активной записи - PullRequest
0 голосов
/ 04 мая 2020

В моем проекте Rails у меня есть таблица с именем "items", где у каждого элемента есть свойство широты и долготы. Когда я получаю элементы для своего клиента, я получаю данные о долготе и широте текущего пользователя. Используя эти два набора координат, я хочу использовать функцию haversine и получить 100 результатов с наименьшим расстоянием haversine.

SQL должно выглядеть примерно так:

SELECT
*,
haversine_distance(user_lat, user_long, item_lat, item_long) as distance
FROM
db.items
WHERE
item_lat < {user_lat} +1
AND item_lat > {user_lat}+1
AND ..
AND ..
ORDER BY distance DESC
LIMIT 100

, но для этого потребуется использовать пользовательскую функцию в запросе, что я не уверен, как это сделать. Кроме того, что касается строк 7 и 8 запроса, я пытаюсь в предложении where быстро отфильтровать набор результатов на некотором разумном расстоянии, поэтому мне не нужно применять haversine ко всей таблице. Прямо сейчас у меня есть функция, определенная в синглтоне с именем DistanceService, но я предполагаю, что не могу использовать функции Ruby в запросе.

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

      @items = Item.select("items.*, haversine_distance(geo_long, geo_lat,
                                  #{@current_user.geo_long}, #{@current_user.geo_lat}) AS distance")
                   .where(status: 'available', ....)

Меня встретили FUNCTION ****_api_development.haversine_distance does not exist. Это наводит меня на мысль, что сначала нужно как-то определить функцию в SQL с помощью некоторой миграции, но я не очень знаком с SQL и не знаю, как бы я это сделал с миграцией.

Мне также интересно, есть ли решение, которое бы поддерживало нумерацию страниц, поскольку, надеюсь, со временем оно станет требованием для моего проекта.

Спасибо.

1 Ответ

1 голос
/ 04 мая 2020

В Rails вы определяете функции базы данных посредством миграций:

class AddHaversineDistanceFunction < ActiveRecord::Migration
  def up
    execute <<~SQL
      DELIMITER $$
      DROP FUNCTION IF EXISTS haversine_distance$$

      CREATE FUNCTION haversine_distance(
        lat1 FLOAT, lon1 FLOAT,
        lat2 FLOAT, lon2 FLOAT
      ) RETURNS FLOAT
      NO SQL DETERMINISTIC
      COMMENT 'Returns the distance in km
             between two known points of latitude and longitude'
      BEGIN
      RETURN DEGREES(ACOS(
        COS(RADIANS(lat1)) *
        COS(RADIANS(lat2)) *
        COS(RADIANS(lon2) - RADIANS(lon1)) +
        SIN(RADIANS(lat1)) * SIN(RADIANS(lat2))
      )) * 111.045;
      END$$

      DELIMITER;
    SQL
  end

  def down
    execute "DROP FUNCTION IF EXISTS haversine_distance"
  end
end

Фактическая функция здесь адаптирована из Plum Island Media .

Но вы можете проверить geocoder gem , который предоставляет эту функциональность и использует лучшую формулу.

...