在 coalesce-d id 上使用自定义连接时访问关联记录
Access associated records when using custom join on coalesce-d id
我的 Rails 应用程序中有两个模型,Organization
和 Warehouse
。 Warehouse
中的条目通过 Warehouse.organization_id
('owned' 个仓库)或 Warehouse.delegated_to_organization_id
('delegated' 个仓库)属于 Organization
中的条目。
一个用例要求我遍历所有仓库及其 'responsible' 组织:'delegating' 组织(如果存在),否则 'owning' 组织。然而,用例要求我将所有仓库按组织分组并分批执行 (1),导致我采用这种方法:
def organizations
Organization.joins(warehouse_join)
end
def warehouse_join
<<~SQL
INNER JOIN warehouses
ON coalesce(
warehouses.delegated_to_organization_id,
warehouses.organization_id,
-1
) = organizations.id
SQL
end
但是,ActiveRecord 似乎忽略了连接:organizations.first.warehouses
总是产生 []
,而使用 ActiveRecord::Base.connection.execute(organizations.to_sql.gsub('"organizations".*', '*')).to_a
执行查询显示来自 warehouses
[=39= 的连接字段].
我哪里做错了,我该如何解决这个问题?
(1)数据量太大无法全部读入内存,所以实际查询是分批执行的(附limit
)
organizations.first.warehouses
只会通过 organization_id
获取 warehouses
。您的 warehouse_join
不会影响此关系。
此外,为了通过查询集合的单个对象获得关联对象而无需额外的数据库查询,您必须使用预加载(预加载)方法,例如 includes
.
例如,如果您想获取 organizations
的列表及其 warehouses
,您应该这样做:
orgs = organizations.includes(:warehouses)
# now you can access `warehouses` of each individual `organization` without extra DB queries
orgs.first.warehouses
orgs.second.warehouses
据我所知,在 ActiveRecord
中不可能(或不太容易)在自定义 JOIN 条件下使用 includes
。
但在你的情况下我不会尝试这样做,还有另一种方法。使用两个单独的关联(每个外键一个):
class Organization < ActiveRecord::Model
# delegated warehouses
has_many :delegated_warehouses, class_name: "Warehouse",
foreign_key: "delegated_to_organization_id"
# owned, but not delegated to any other organization
has_many :owned_not_delegated_warehouses, -> { where(delegated_to_organization_id: nil) },
class_name: "Warehouse",
foreign_key: organization_id
def warehouses_under_responsibility
# do not do like this!!! those associations are for preloading only!
delegated_warehouses + owned_not_delegated_warehouses # wrong!!!
# the right way
Warehouse.where("coalesce(delegated_to_organization_id, organization_id, -1) = ?", id)
end
end
现在您可以使用关联单独预加载(预先加载)warehouses
负责。
orgs = Organization.includes(:delegated_warehouses, :owned_not_delegated_warehouses)
orgs.each do |org|
# now you can access the associated records separately
owned_warehouses = org.owned_not_delegated_warehouses
delegated_warehouses = org.delegated_warehouses
# or use them all together
all_warehouses = owned_warehouses + delegated_warehouses
end
注:
这些关联只应在预加载仓库时使用,即当有大量组织集合并且您需要获取它们负责的仓库时。
如果您只有一个组织并且需要获取它负责的仓库,您应该使用另一种方法,如 warehouses_under_responsibility
方法所示。
了解为什么我们在预加载时没有使用相同的方法很重要。那是因为我们不能使 include
与自定义连接条件一起工作,即 coalesce(delegated_to_organization_id, organization_id, -1) = organizations.id
我的 Rails 应用程序中有两个模型,Organization
和 Warehouse
。 Warehouse
中的条目通过 Warehouse.organization_id
('owned' 个仓库)或 Warehouse.delegated_to_organization_id
('delegated' 个仓库)属于 Organization
中的条目。
一个用例要求我遍历所有仓库及其 'responsible' 组织:'delegating' 组织(如果存在),否则 'owning' 组织。然而,用例要求我将所有仓库按组织分组并分批执行 (1),导致我采用这种方法:
def organizations
Organization.joins(warehouse_join)
end
def warehouse_join
<<~SQL
INNER JOIN warehouses
ON coalesce(
warehouses.delegated_to_organization_id,
warehouses.organization_id,
-1
) = organizations.id
SQL
end
但是,ActiveRecord 似乎忽略了连接:organizations.first.warehouses
总是产生 []
,而使用 ActiveRecord::Base.connection.execute(organizations.to_sql.gsub('"organizations".*', '*')).to_a
执行查询显示来自 warehouses
[=39= 的连接字段].
我哪里做错了,我该如何解决这个问题?
(1)数据量太大无法全部读入内存,所以实际查询是分批执行的(附limit
)
organizations.first.warehouses
只会通过 organization_id
获取 warehouses
。您的 warehouse_join
不会影响此关系。
此外,为了通过查询集合的单个对象获得关联对象而无需额外的数据库查询,您必须使用预加载(预加载)方法,例如 includes
.
例如,如果您想获取 organizations
的列表及其 warehouses
,您应该这样做:
orgs = organizations.includes(:warehouses)
# now you can access `warehouses` of each individual `organization` without extra DB queries
orgs.first.warehouses
orgs.second.warehouses
据我所知,在 ActiveRecord
中不可能(或不太容易)在自定义 JOIN 条件下使用 includes
。
但在你的情况下我不会尝试这样做,还有另一种方法。使用两个单独的关联(每个外键一个):
class Organization < ActiveRecord::Model
# delegated warehouses
has_many :delegated_warehouses, class_name: "Warehouse",
foreign_key: "delegated_to_organization_id"
# owned, but not delegated to any other organization
has_many :owned_not_delegated_warehouses, -> { where(delegated_to_organization_id: nil) },
class_name: "Warehouse",
foreign_key: organization_id
def warehouses_under_responsibility
# do not do like this!!! those associations are for preloading only!
delegated_warehouses + owned_not_delegated_warehouses # wrong!!!
# the right way
Warehouse.where("coalesce(delegated_to_organization_id, organization_id, -1) = ?", id)
end
end
现在您可以使用关联单独预加载(预先加载)warehouses
负责。
orgs = Organization.includes(:delegated_warehouses, :owned_not_delegated_warehouses)
orgs.each do |org|
# now you can access the associated records separately
owned_warehouses = org.owned_not_delegated_warehouses
delegated_warehouses = org.delegated_warehouses
# or use them all together
all_warehouses = owned_warehouses + delegated_warehouses
end
注:
这些关联只应在预加载仓库时使用,即当有大量组织集合并且您需要获取它们负责的仓库时。
如果您只有一个组织并且需要获取它负责的仓库,您应该使用另一种方法,如 warehouses_under_responsibility
方法所示。
了解为什么我们在预加载时没有使用相同的方法很重要。那是因为我们不能使 include
与自定义连接条件一起工作,即 coalesce(delegated_to_organization_id, organization_id, -1) = organizations.id