在SQLAlchemy中使用查询结果进行联合汇总统计?

时间:2016-11-22 00:44:23

标签: postgresql sqlalchemy flask-sqlalchemy psycopg2

我有一个PostgreSQL表,用于存储功率计的读数。我使用SQLAlchemy和psycopg2来查询数据库。一些大型站点可以有多个功率计,我有一个查询返回带时间戳的数据,由设施汇总:

原始表:

timestamp | meter_id | facility_id | reading
  1:00:00 |        1 |           1 |     1.0
  1:00:00 |        2 |           1 |     1.5
  1:00:00 |        3 |           2 |     2.1
  1:00:30 |        1 |           1 |     1.1
  1:00:30 |        2 |           1 |     1.6
  1:00:30 |        3 |           2 |     2.2

汇总:

timestamp | facility_1 | facility_2
  1:00:00 |        2.5 |       2.1
  1:00:30 |        2.7 |       2.2

我用于此的查询如下所示:

SELECT
    reading.timestamp,
    sum(reading.reading) FILTER (WHERE reading.facility_id = 1) as facility_1,
    sum(reading.reading) FILTER (WHERE reading.facility_id = 2) as facility_2
FROM reading
GROUP BY reading.timestamp
WHERE
    reading.timestamp >= 1:00:00 AND reading.timestamp < 1:01:00
    AND reading.facility_id IN 1, 2

(对不起任何SQL错误,为了清晰起见,我已经简化了问题)。我经常需要对数据进行下采样以进行显示,这是通过将上述查询包装在FROM ... AS ...子句中并将数据合并到更大的时间间隔中来实现的。不过,在此之前,我想从我的衍生设施&#34;设施中获取一些摘要统计数据。表 - 最小读数,最大读数,平均读数等,类似于this blog post中描述的内容。但是,我无法弄清楚如何使用SQLALchemy来获取此数据 - 我不断从生成的SQL中获取psycopg2错误。我上面查询的SQLAlchemy版本是:

selects = [Reading.timestamp,
    sqlalchemy.func.sum(Reading.reading).filter(Reading.facility_id==1),
    sqlalchemy.func.sum(Reading.reading).filter(Reading.facility_id==2)
]
base_query = db.session.query(*selects). \
    group_by(Reading.timestamp). \
    filter(Reading.facility_id.in_([1, 2])). \
    filter(and_(Reading.timestamp>=start_time, Reading.timestamp<=end_time)). \
    order_by(Reading.timestamp)

我可以使用以下内容获取摘要统计信息:

subq = base_query.subquery()
avg_selects = [sqlalchemy.func.avg(col) for col in subq.columns]
avg_query = db.session.query(*avg_selects)

这将返回一行,其中包含原始查询中所有列的平均值。但是,我无法弄清楚如何使用我的原始查询得到这个 - 我最终必须单独获取统计信息,这感觉像是一个巨大的浪费(这些查询可以遍历许多行)。下面的查询总是返回错误:

all = base_query.union(avg_query).all()

ProgrammingError: (psycopg2.ProgrammingError) syntax error at or near "UNION"
LINE 4: ...reading.timestamp ORDER BY reading.timestamp UNION SELE...

我觉得我对SQLAlchemy的子查询系统的理解很弱,但我还没有能够从SQLAlchemy的文档中的子查询教程中取得进展。想法?

1 个答案:

答案 0 :(得分:0)

错误消息中的答案是正确的 - 我需要将子查询中的ORDER BY子句移除到union操作之外,并将其移出UNION之外。我在汇总统计信息中使用虚拟时间戳,以确保在按时间戳排序后,它们以可预测的顺序位于查询结果的顶部。以下代码有效:

from sqlalchemy.sql import expression, func
from datetime import datetime
from models import Reading

selects = [Reading.timestamp.label("timestamp_"),
    func.sum(Reading.reading).filter(Reading.facility_id==1),
    func.sum(Reading.reading).filter(Reading.facility_id==2)
]

base_query = db.session.query(*selects). \
    group_by(Reading.timestamp). \
    filter(Reading.facility_id.in_([1, 2])). \
    filter(and_(Reading.timestamp>=start_time, Reading.timestamp<=end_time))

subq = base_query.subquery()

avg_selects = [expression.bindparam('dummy_date', datetime(1980, 1, 1)).label("timestamp_")
avg_selects += [func.avg(col) for col in subq.columns[1:]
avg_query = db.session.query(*avg_selects)

full_query = base_query.union(avg_query).order_by(asc("timestamp_"))

我很高兴听到更多优雅的方式来实现这一目标。查询包含在一个函数中,该函数接受任意设施ID列表; &#34;列&#34;技巧是我发现使其适用于任意列的唯一方法(只要第一列始终是时间戳)。