【问题标题】:Query a chain of one to many and many to one查询一对多和多对一的链
【发布时间】:2017-04-01 11:36:18
【问题描述】:

我目前有 3 个表,大致描述为以下 SQLAlchemy 映射:

class Task(BASE):
    __tablename__ = 'tasks'
    id = Column(Integer, primary_key=True)

    service_id = Column(Integer, ForeignKey('services.id'))
    service = relationship('Service', back_populates="tasks")

    updates = relationship("TaskUpdate")


class TaskUpdate(BASE):
    __tablename__ = 'task_updates'

    id = Column(Integer, primary_key=True)
    external_status = Column(String(32))
    external_updated_at = Column(DateTime(timezone=True))

    task_id = Column(Integer, ForeignKey('tasks.id'))
    task = relationship('Task', back_populates="updates")


class Service(BASE):
    __tablename__ = 'services'

    id = Column(Integer, primary_key=True)

    client_id = Column(Integer, ForeignKey('clients.id'))
    client = relationship('Client', back_populates='services')

所以我有从 Task 到 TaskUpdates 的一对多关系以及从 Task 到 Service 的多对一关系。

我正在尝试创建一个查询以获取所有任务,其中最新的 TaskUpdate(按时间戳)具有“新建”或“打开”的 external_status。

这是我得到的:

sub = SESSION.query(
        TaskUpdate.task_id,
        TaskUpdate.external_status.label('last_status'),
        func.max(TaskUpdate.external_updated_at).label('last_update')
        ).group_by(TaskUpdate.task_id
        ).subquery()
tasks = SESSION.query(Task
        ).join(Service
        ).filter(Service.client_id == client_id
        ).join((sub, sub.c.task_id == Task.id)
        ).filter(sub.c.last_status.in_(['New', 'Open']))

当我运行这个时,我得到这个错误:

ProgrammingError: (psycopg2.ProgrammingError) column "task_updates.external_status" must appear in the GROUP BY clause or be used in an aggregate function

如果您能提供任何帮助,我将不胜感激。这很重要。

更新 1(这是最终工作的 SQL(据我所知,我无法测试前端,直到我在 SQLAlchemy 中使用它:

SELECT t.* FROM ( 
  SELECT DISTINCT ON (task_id) task_id, external_status 
  FROM task_updates 
  ORDER BY task_id, external_updated_at DESC NULLS LAST) tu 
JOIN tasks t ON t.id = tu.task_id 
JOIN services s ON s.id = t.service_id 
WHERE s.client_id = '" + str(client_id) + "' 
AND tu.external_status IN ('New', 'Open');

这是我的转换尝试,仍然无法正常工作:

sub = SESSION.query(TaskUpdate).distinct(TaskUpdate.task_id).order_by(TaskUpdate.task_id.desc().nullslast(), TaskUpdate.external_updated_at.desc().nullslast()).subquery()
tasks = SESSION.query(Task).join(Service).join(sub.c.task_id==Task.id).filter(TaskUpdate.external_status.in_(['New', 'Open']))

更新 2:我在下面的查询有效,但是当我执行 .count() 时,它返回 TaskUpdates 的总数,而不是任务,我怀疑查询需要以不同的方式重做,除非有人知道方法处理这个?

【问题讨论】:

    标签: python postgresql sqlalchemy flask-sqlalchemy greatest-n-per-group


    【解决方案1】:

    正在这样做:

    SELECT t.*
    FROM  (
       SELECT DISTINCT ON (task_id)
              task_id, external_status
       FROM   task_updates
       ORDER  BY task_id, external_updated_at DESC NULLS LAST
       ) tu
    JOIN   tasks t ON t.id = tu.task_id
    WHERE  tu.external_status IN ('New', 'Open');
    

    首先获取每个任务的最后一行,然后只选择正确的任务external_status

    DISTINCT ON的详细解释:

    如果每个任务有很多行,有更快的查询技术:

    【讨论】:

    • SELECT t.* FROM (SELECT DISTINCT ON (task_id) task_id, external_status FROM task_updates ORDER BY task_id, external_updated_at DESC NULLS LAST) tu JOIN tasks t ON t.id = tu.task_id JOIN services s ON s.id = t.service_id WHERE s.client_id = '1' AND tu.external_status IN ('New', 'Open');这是最后一个有效的查询,谢谢。现在我需要将其转换为 SQLAlchemy。
    • 如果您对此查询进行计数,它会返回 TaskUpdates 的总数。这不是理想的行为。有没有更干净的?
    • @PhilSalesses:如果您对此查询进行计数,它不会返回 TaskUpdate 的总数。您将获得tasks 中至少在task_updates 中有一个相关行的行数。一定是哪里有误会。
    【解决方案2】:

    我要感谢 Erwin,因为他让我走上了正确的道路,但这是我最终使用的。工作得很好。一旦我真正有一个或几个工程师和我一起工作,以后会优化。 :)

    谢谢!

    sub = SESSION.query(TaskUpdate.task_id, TaskUpdate.external_status).distinct(TaskUpdate.task_id).order_by(TaskUpdate.task_id.desc().nullslast(), TaskUpdate.external_updated_at.desc().nullslast()).subquery()
    tasks = SESSION.query(Task).join(Service).join((sub, sub.c.task_id==Task.id)).filter(sub.c.external_status.in_(['New', 'Open', 'Pending']))
    

    也许我错误地转换了这个,但是当我计算时,它给了我任务更新的数量,而不是任务。这会导致我的应用出现问题。

    【讨论】:

      【解决方案3】:

      这是获得所需结果的一种方法:

      在 SQL 中(已测试):

      SELECT a.task_id, a.external_status, a.external_updated_at
      FROM ( 
        SELECT task_id, max(external_updated_at) AS last_updated_at
        FROM task_updates 
        GROUP BY task_id
      ) b 
      JOIN task_updates a ON a.task_id = b.task_id
      WHERE
        a.external_updated_at = b.last_updated_at AND
        a.external_status IN ('New', 'Open')
      ORDER BY
        a.task_id;
      

      在 Python/SQLAlchemy 中(尚未测试,暂时没有 SQLAlchemy):

      subq = session.query(
          TaskUpdate.task_id, func.max(TaskUpdate.external_updated_at).label('last_updated_at')
        ).group_by(
          TaskUpdate.task_id
        ).subquery()
      
      q = session.query(
          TaskUpdate.task_id, TaskUpdate.external_status, TaskUpdate.external_updated_at
        ).join(
          TaskUpdate.task_id == subq.c.task_id)
        ).filter(
          TaskUpdate.external_updated_at == sub.c.last_updated_at,
          TaskUpdate.external_status.in_(['New', 'Open'])
        ).order_by(
          TaskUpdate.task_id
        )
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-13
        • 2014-09-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多