Я использую слегка модифицированную версию геокодированного гема , которая возвращает этот запрос, когда я вызываю near
для моей модели (вызывая Deal.near(southwest)
, где southwest
- это массив гео координаты):
SELECT
deals.*,
3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((37.772476604436974 - addresses.lat) * PI() / 180 / 2), 2) + COS(37.772476604436974 * PI() / 180) * COS(addresses.lat * PI() / 180) * POWER(SIN((-122.42336332798004 - addresses.lng) * PI() / 180 / 2), 2) )) AS distance,
CAST(DEGREES(ATAN2( RADIANS(addresses.lng - -122.42336332798004), RADIANS(addresses.lat - 37.772476604436974))) + 360 AS decimal) % 360 AS bearing
FROM "deals"
INNER JOIN "companies" ON "companies"."id" = "deals"."company_id"
INNER JOIN "addresses" ON "addresses"."addressable_id" = "companies"."id" AND "addresses"."addressable_type" = 'Company'
WHERE (
addresses.lat BETWEEN 37.483013038215276 AND 38.06194017065867
AND addresses.lng BETWEEN -122.78956461309022 AND -122.05716204286986
)
GROUP BY
deals.id,
deals.created_at,
deals.updated_at,
deals.active,
deals.company_id,
deals.title,
deals.limitations,
deals.redemption_count,
addresses.lat,
addresses.lng
HAVING 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((37.772476604436974 - addresses.lat) * PI() / 180 / 2), 2) + COS(37.772476604436974 * PI() / 180) * COS(addresses.lat * PI() / 180) * POWER(SIN((-122.42336332798004 - addresses.lng) * PI() / 180 / 2), 2) )) <= 20
ORDER BY 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((37.772476604436974 - addresses.lat) * PI() / 180 / 2), 2) + COS(37.772476604436974 * PI() / 180) * COS(addresses.lat * PI() / 180) * POWER(SIN((-122.42336332798004 - addresses.lng) * PI() / 180 / 2), 2) )) ASC
Моя проблема в том, что это вернет несколько Deal
записей, если у этой Deal
компании есть несколько Address
es, которые я не хочу.
В MySQL я мог бы просто опустить address.lat, address.lng
в предложении GROUP_BY
, и он будет правильно группировать записи, но я не могу сделать это в PostgreSQL.
Я знаю, что могу обернуть весь запрос выше в другие SELECT
и GROUP_BY
, например:
SELECT
id, created_at, updated_at, active, title, punches_to_complete, company_id, description, lat, lng, MIN(distance), bearing
FROM ( ... ) AS t
GROUP BY company_id
... где многоточие - это запрос сверху. Это (я считаю) должно дать мне желаемый результат как в MySQL, так и в PostgreSQL.
Единственная проблема в том, что я понятия не имею, как написать это в ARel!
Я попробовал следующее, а этот совет от гуру ARel, но я не мог заставить его работать совершенно правильно (звонил to_sql
, так как ОП сказал, что исправил, его проблема ускользает от цитаты, которые выходят из PostgreSQL).
Может кто-нибудь помочь мне с этим ???
UPDATE
Мне удалось сделать это с помощью дополнительного объема, например:
scope :nearest, lambda { |coords|
subquery = "(#{Deal.near(coords).to_sql}) AS t1"
columns = Deal.columns.map{ |c| c.name }.join(',')
Deal.select(columns)
.select('MIN(distance) AS distance')
.from(subquery)
.group(columns)
.order('distance ASC')
}
Однако это полностью нарушает цепочечность, так как теперь я не могу назвать что-то вроде current_user.deals.nearest(coords)
, так как это помечает дополнительный WHERE deals.user_id = 1
к запросу за пределами подвыбора. Я попытался компенсировать это, переместив эту логику в метод класса и вручную отключив выражение wheres в SelectManager, например:
def self.nearest(coords)
subquery = "(#{Deal.near(coords).to_sql}) AS t1"
columns = Deal.columns.map{ |c| c.name }.join(',')
query = Deal.select(columns)
.select('MIN(distance) AS distance')
.from(subquery)
.group(columns)
.order('distance ASC')
query.arel.ast.cores[0].wheres = []
query
end
... но, похоже, это тоже не работает: дополнительное условие WHERE
все еще добавляется:
Сбой / Ошибка:
@ user.deals.nearest (юго-запад) .first.distance.to_f.round (2) .should ==
ActiveRecord :: StatementInvalid:
Mysql2 :: Ошибка: Неизвестный столбец "deal.user_id" в "где
предложение ': идентификатор SELECT, создан_кат, обновлен_ат, идентификатор_пользователя, идентификатор_компании,
MIN (расстояние) AS расстояние от (выберите предложения. *, 3958.755864232 * 2 *
ASIN (SQRT (POWER (SIN ((37.772476604436974 - address.lat) * PI () / 180)
/ 2), 2) + COS (37,772476604436974 * PI () / 180) * COS (address.lat *)
PI () / 180) * POWER (SIN ((- 122,42336332798004 - address.lng) * PI () /
180/2), 2))) AS расстояние, CAST (ГРАДУСЫ (ATAN2 (RADIANS (address.lng)
- -122.42336332798004), RADIANS (address.lat - 37.772476604436974)))
+ 360 как десятичное)% 360 как подшипник deals
INNER JOIN companies
ON companies
. id
= deals
. company_id
INNER JOIN addresses
ON
addresses
. addressable_id
= companies
. id
И
addresses
. addressable_type
= 'Компания' ГДЕ deals
. user_id
=
26 И (адреса ЛАТ МЕЖДУ 37.483013038215276 И 38.06194017065867
AND address.lng МЕЖДУ -122.78956461309022 И -122.05716204286986)
ГРУППА ПО
deals.id, deals.created_at, deals.updated_at, deals.user_id, deals.company_id,
address.lat, address.lng HAVING 3958.755864232 * 2 *
ASIN (SQRT (POWER (SIN ((37.772476604436974 - address.lat) * PI () / 180)
/ 2), 2) + COS (37,772476604436974 * PI () / 180) * COS (address.lat *)
PI () / 180) * POWER (SIN ((- 122,42336332798004 - address.lng) * PI () /
180/2), 2))) <= 20 ЗАКАЗАТЬ НА 3958.755864232 * 2 *
ASIN (SQRT (POWER (SIN ((37.772476604436974 - address.lat) * PI () / 180)
/ 2), 2) + COS (37,772476604436974 * PI () / 180) * COS (address.lat *)
PI () / 180) * POWER (SIN ((- 122,42336332798004 - address.lng) * PI () /
180/2), 2))) ASC) AS t1 ГДЕ <code>deals. user_id
= 26 GROUP BY
идентификатор, создан_кат, обновлен_ат, user_id, company_id ORDER BY расстояние ASC
LIMIT 1
Возможно ли то, что я пытаюсь сделать, даже с ARel? Дополнительные области видимости мне действительно кажутся грязными (парсинг подзапроса в сырой SQL? Я думал, что ARel должен был это сделать, поэтому я никогда этого не делал!)
Смежный вопрос: может ли ARel формулировать перекрестные дБ-запросы для CTE (общих табличных выражений)?