Арель выбирает с ActiveRecord? - PullRequest
       7

Арель выбирает с ActiveRecord?

2 голосов
/ 21 сентября 2011

Я использую слегка модифицированную версию геокодированного гема , которая возвращает этот запрос, когда я вызываю 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 (общих табличных выражений)?

...