Эффективный подзапрос SQLAlchemy для последнего значения
Текущее значение сущности status
Атрибут может запрашиваться как последняя запись в таблице EntityHistory для этого объекта, т.е.
Entities (id) <- EntityHistory (timestamp, entity_id, value)
Как мне написать эффективное выражение SQLALchemy, которое охотно загружает текущее значение из таблицы истории для всех сущностей, не приводя к N+1 запросам?
Я попытался написать свойство для моей модели, но при этом итерация по нему генерирует запрос для каждого (N+1). Насколько мне известно, нет способа решить эту проблему без подзапроса, который все еще кажется мне неэффективным в базе данных.
пример EntityHistory
данные:
timestamp |entity_id| value
==========|=========|======
15:00| 1| x
15:01| 1| y
15:02| 2| x
15:03| 2| y
15:04| 1| z
Таким образом, текущее значение для сущности 1 будет z
и для субъекта 2 это будет y
, Резервная база данных - Postgres.
1 ответ
Я думаю, что вы могли бы использовать column_property
загрузить последнее значение в качестве атрибута Entities
экземпляр вдоль других атрибутов, сопоставленных со столбцами:
from sqlalchemy import select
from sqlalchemy.orm import column_property
class Entities(Base):
...
value = column_property(
select([EntityHistory.value]).
where(EntityHistory.entity_id == id). # the id column from before
order_by(EntityHistory.timestamp.desc()).
limit(1).
correlate_except(EntityHistory)
)
Подзапрос, конечно, может также использоваться в запросе вместо column_property
,
query = session.query(
Entities,
session.query(EntityHistory.value).
filter(EntityHistory.entity_id == Entities.id).
order_by(EntityHistory.timestamp.desc()).
limit(1).
label('value')
)
Производительность, естественно, зависит от наличия соответствующего индекса:
Index('entityhistory_entity_id_timestamp_idx',
EntityHistory.entity_id,
EntityHistory.timestamp.desc())
В некотором смысле это все еще ваш страшный N+1, так как запрос использует подзапрос на строку, но он скрыт в одном цикле передачи в БД.
Если, с другой стороны, имеет значение как свойство Entities
не требуется, в Postgresql вы можете присоединиться с помощью запроса DISTINCT ON ... ORDER BY, чтобы получить последние значения:
values = session.query(EntityHistory.entity_id,
EntityHistory.value).\
distinct(EntityHistory.entity_id).\
# The same index from before speeds this up.
# Remember nullslast(), if timestamp can be NULL.
order_by(EntityHistory.entity_id, EntityHistory.timestamp.desc()).\
subquery()
query = session.query(Entities, values.c.value).\
join(values, values.c.entity_id == Entities.id)
хотя в ограниченном тестировании с фиктивными данными столбец subquery-as-output-всегда всегда опережает объединение с заметным запасом, если у каждого объекта есть значения. С другой стороны, если были миллионы сущностей и много пропущенных значений истории, то ЛЕВОЕ СОЕДИНЕНИЕ было быстрее. Я бы порекомендовал протестировать ваши данные, какой запрос лучше подходит для ваших данных. Для произвольного доступа одного объекта с учетом наличия индекса коррелированный подзапрос выполняется быстрее. Для массовых загрузок: тест.