Добавить вычисляемый столбец в предложение выбора нескольких таблиц с помощью eager_load в Ruby on Rails Activerecord
У меня есть запрос с большим количеством объединений, и я eager_load
с некоторыми ассоциациями в то время. И мне нужно вычислить некоторое значение как атрибут одной из моделей.
Итак, я пробую этот код:
ServiceObject
.joins([{service_days: :ou}, :address])
.eager_load(:address, :service_days)
.where(ous: {id: OU.where(sector_code: 5)})
.select('SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')
Где вызов функции SQL в select
управляет данными из связанных таблиц addresses
и ous
.
Я получаю следующий SQL (поэтому мой столбец in_zone
вычисляется и возвращается как первый столбец перед другими столбцами для всех моделей eager_load
ed):
SELECT SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone, "SERVICE_OBJECTS"."ID" AS t0_r0, "SERVICE_OBJECTS"."TYPE" AS t0_r1, <omitted for brevity> AS t2_r36 FROM "SERVICE_OBJECTS" INNER JOIN "SERVICE_DAYS" ON "SERVICE_DAYS"."SERVICE_OBJECT_ID" = "SERVICE_OBJECTS"."ID" INNER JOIN "OUS" ON "OUS"."ID" = "SERVICE_DAYS"."OU_ID" INNER JOIN "ADDRESSES" ON "ADDRESSES"."ID" = "SERVICE_OBJECTS"."ADDRESS_ID" WHERE "OUS"."ID" IN (SELECT "OUS"."ID" FROM "OUS" WHERE "OUS"."SECTOR_CODE" = :a1) [["sector_code", "5"]]
Но похоже, что in_zone
недоступен из любой модели, используемой в запросе.
Мне нужно вычислить in_zone
как атрибут объекта модели ServiceObject
, как я могу это сделать?
Ruby on Rails 4.2.6, Ruby 2.3.0, oracle_enhanced adapter 1.6.7, Oracle 12.1
Ответы
Ответ 1
Этот патч обезьяны помог @Envek:
module ActiveRecord
Base.send :attr_accessor, :_row_
module Associations
class JoinDependency
JoinBase && class JoinPart
def instantiate_with_row(row, *args)
instantiate_without_row(row, *args).tap { |i| i._row_ = row }
end; alias_method_chain :instantiate, :row
end
end
end
end
то можно сделать:
ServiceObject
.joins([{service_days: :ou}, :address])
.eager_load(:address, :service_days)
.where(ous: {id: OU.where(sector_code: 5)})
.select('SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')
.first
._row_['in_zone']
Ответ 2
Я успешно реплицировал вашу проблему, и оказалось, что это известная проблема в Rails. Проблема в том, что при использовании eager_load
Rails отображает столбцы всех загруженных таблиц в псевдонимы таблиц и столбцов в форме t0_r0
, t0_r1
и т.д. (вы можете увидеть их в SQL, которые вы вставили в вопрос). И при этом он просто игнорирует пользовательские столбцы в select
, возможно, потому, что он не может определить, какую загруженную таблицу должен привязать пользовательский столбец. Печально, что этот вопрос открыт уже более двух лет...
Тем не менее, я думаю, что нашел обходной путь. Кажется, что если вы не хотите загружать таблицы, но вручную присоединяете их (с помощью joins
), вы можете также включить их (с includes
) и пользовательские столбцы будут возвращены как не будет никакого алиасинга столбца. Дело в том, что вы не должны использовать ассоциации в предложениях joins
, но вы должны сами указывать объединения. Также обратите внимание, что вы должны также указывать все столбцы из основной таблицы в ручном выборе (см. service_objects.*
в выборе).
Попробуйте выполнить следующий подход:
ServiceObject
.joins('INNER JOIN "SERVICE_DAYS" ON "SERVICE_DAYS"."SERVICE_OBJECT_ID" = "SERVICE_OBJECTS"."ID"')
.joins('INNER JOIN "OUS" ON "OUS"."ID" = "SERVICE_DAYS"."OU_ID"')
.joins('INNER JOIN "ADDRESSES" ON "ADDRESSES"."ID" = "SERVICE_OBJECTS"."ADDRESS_ID"')
.includes(:service_days, :address)
.where(ous: {id: OU.where(sector_code: 5)})
.select('service_objects.*, SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')
Вычисление в select должно по-прежнему работать, поскольку связанные таблицы join
ed вместе, но не должно присутствовать псевдонимы столбцов.
Конечно, этот подход означает, что вы получите три запроса вместо одного, но если вы не вернете огромное количество записей, следующие два запроса, выполняемые предложением includes
, должны быть очень быстрыми, поскольку они просто загружают соответствующие записи с использованием внешних ключей.