查询一对多的链和多对一

时间:2017-04-01 11:36:18

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

我目前有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,即" New"或" Open。​​"

这是我得到的:

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的总数,而不是任务,我怀疑查询需要以不同的方式重做,除非有人知道某种方式处理这个?

3 个答案:

答案 0 :(得分: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的详细说明:

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

答案 1 :(得分:0)

我赞扬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']))

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

答案 2 :(得分:0)

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

在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
  )