SQLAlchemy: выберите самую последнюю строку для всех идентификаторов в одной таблице с составным первичным ключом - PullRequest
0 голосов
/ 19 июня 2019

Я хочу сделать это , но в SQLAlchemy.Единственное отличие состоит в том, что вместо того, чтобы получать только самую последнюю запись, я хочу иметь возможность получить самую последнюю запись до заданной отметки времени.Пока я гарантирую, что строки никогда не удаляются, это позволяет мне просматривать базу данных, которая была на определенной временной метке.

Допустим, моя модель выглядит следующим образом:

from datetime import datetime
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.ext.declarative include declarative_base
Base = declarative_base()
class User(Base):
    __tablename__ = "users"
    id_ = Column("id", Integer, primary_key=True, index=True, nullable=False)
    timestamp = Column(DateTime, primary_key=True, index=True, nullable=False, default=datetime.utcnow())
    # other non-primary attributes would go here

И яиметь эту таблицу users (временные метки упрощены):

| id_ | timestamp |
-------------------
  0     1
  0     4
  0     6
  1     3
  2     7
  2     3

Например, если я запрашиваю снимок в timestamp = 4, я хочу получить:

| id_ | timestamp |
-------------------
  0     4
  1     3
  2     3

Лучшее, что яможет придумать, делает это процедурно:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
db_engine = create_engine(...)
SessionLocal = sessionmaker(bind=db_engine, ...)
db_session = SessionLocal()

def get_snapshot(timestamp: datetime):
    all_versions = db_session.query(User).filter(User.timestamp <= timestamp).order_by(desc(User.timestamp))
    snapshot = []
    for v in all_versions:
        if v.id_ not in (i.id_ for i in snapshots):
            snapshot.append(v)
    return snapshot

Однако, это дает мне список объектов модели, а не sqlalchemy.orm.query.Query, поэтому я должен трактовать результат иначе, чем стандартные запросы в других частяхмой кодМожно ли все это сделать в ORM?

Заранее спасибо

Ответы [ 2 ]

0 голосов
/ 20 июня 2019

Альтернативой решению Matteo является использование подзапроса и присоединение его к таблице, что дает результат в моем предпочтительном формате объекта sqlalchemy.orm.query.Query. Кредит Matteo за код для подзапроса:

subq = db_session.query(User.id_, func.max(User.timestamp).label("maxtimestamp")).filter(User.timestamp < timestamp).group_by(User.id_).subquery()
q = db_session.query(User).join(subq, and_(User.id_ == subq.c.id, User.timestamp == subq.c.maxtimestamp))

Генерация SQL

Обратите внимание, что это, вероятно, менее эффективно, чем решение Matteo:

SQL, сгенерированный решением подзапроса

SELECT users.id AS users_id, users.timestamp AS users_timestamp, users.name AS users_name, users.notes AS users_notes, users.active AS users_active
FROM users JOIN (SELECT users.id AS id, max(users.timestamp) AS maxtimestamp
FROM users
WHERE users.timestamp < ? GROUP BY users.id) AS anon_1 ON users.id = anon_1.id AND users.timestamp = anon_1.maxtimestamp

SQL, сгенерированный решением Маттео:

SELECT users.id AS users_id, users.timestamp AS users_timestamp, users.name AS users_name, users.notes AS users_notes, users.active AS users_active, max(users.timestamp) AS max_1
FROM users
WHERE users.timestamp <= ? GROUP BY users.id

Предыдущее содержание этого ответа

@ Маттео Ди Наполи

Спасибо, ваш пост более или менее нужен мне. Результатом этого является sqlalchemy.util._collections.result, который ведет себя как кортеж из того, что я вижу. В моем приложении мне нужны полные User объекты, а не просто пары id / timestamp, поэтому для меня лучше всего подойдет:

from sqlalchemy import func 

all_versions = db_session.query(User, func.max(User.timestamp)).\
               filter(User.timestamp <= timestamp).\
               group_by(User.id_)

Возвращая что-то вроде:

> for i in all_versions: print(i)
...
(<User "my test user v2", id 0, modified 2019-06-19 14:42:16.380381>, datetime.datetime(2019, 6, 19, 14, 42, 16, 380381))
(<User "v2", id 1, modified 2019-06-19 15:53:53.147039>, datetime.datetime(2019, 6, 19, 15, 53, 53, 147039))
(<User "a user", id 2, modified 2019-06-20 12:34:56>, datetime.datetime(2019, 6, 20, 12, 34, 56))

Затем я могу получить доступ к объектам пользователя с помощью all_versions[n][0] или получить список с помощью l = [i[0] for i in all_versions] (спасибо Маттео Ди Наполи за лучший синтаксис).

Идеальный конечный результат был бы, если бы я мог получить результат, который все еще будет sqlalchemy.orm.query.Query (например, all_versions), но с каждым элементом будет объект User, а не sqlalchemy.util._collections.result. Это возможно?

0 голосов
/ 20 июня 2019

Вы пробовали:

all_versions = db_session.query(User, func.max(User.timestamp)).\
               filter(User.timestamp <= timestamp).\
               group_by(User.id_)               

Вы можете узнать больше о универсальных функциях в SQLAlchemy здесь

...