我在使用 Ecto
进行查询时遇到一些问题:
SavedDevice
- belongs_to Devices
- belongs_to User
Device
- has_many SavedDevices
- has_many DeviceLocations
DeviceLocation
- belongs_to Devices
我想要加载属于用户的SavedDevices,并保存最新的DeviceLocation。
这个解决方案工作正常:
def list_pdevices_locations(user, limit \\ 0) do
location_query = from(dl in DeviceLocation, order_by: [desc: :inserted_at])
query =
from(d in ProvisionedDevice,
preload: [device_info: [locations: ^location_query]],
limit: ^limit
)
if user.is_admin do
Repo.all(query)
else
Repo.all(from(d in query, where: d.user_id == ^user.id))
end
end
但它会加载每个DeviceLocations。这是一个问题,因为可能有数千个,而我只需要最后一个。
当我将 limit: 1
添加到 location_query
时,它只为所有设备返回 1 个DeviceLocation,并且不是 1 个设备位置每设备。
有什么想法吗?
最佳答案
我不知道这是否可以在预加载中完成,但您可以使用 lateral joins (第 7.2.1.5 节)以实现您想要的目标。您将需要为位置创建子查询:
location_query =
from(dl in DeviceLocation,
where: parent_as(:d_info).id == dl.device_info_id,
# Unique rows by device_info_id
distinct: dl.device_info_id,
# Order by inserted at to get the most recent entry
order_by: [desc: dl.inserted_at]
)
然后从 ProvisionedDevice 加入它:
query =
from(pd in ProvisionedDevice,
inner_join: di as assoc(pd, :device_info),
# Use the same alias as specified in the subquery
as: :d_info,
inner_lateral_join: dl in subquery(location_query),
on: dl.device_info_id == di.id,
# You can structure this select any way you like
select: %{device: pd, info: di, location: dl}
)
当然,这将不再符合您示例中的返回规范,但您可以对结果进行后期处理以符合您之前的契约(Contract)(如果需要):
for %{device: device, info: info, location: location} <- Repo.all(query) do
info_and_location = %{info | locations: [location]}
%{device | device_info: info_and_location}
end
无论哪种方式,查询都会在一个查询中为您提供一台设备及其信息和最近位置。
关于elixir - Ecto 对嵌套关联的限制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69100027/