我正在尝试将相当短的SQL转换为sqlAlchemy ORM查询。SQL使用Postgresgenerate_series来创建一组日期,而我的目标是创建一组按列之一分类的时间序列数组。
generate_series
这些表(简化后)非常简单:
counts: ----------------- count (Integer) day (Date) placeID (foreign key related to places) "counts_pkey" PRIMARY KEY (day, placeID) places: ----------------- id name (varchar)
我需要的输出是每个地方的时间序列,包括当一天未报告计数时的空值。例如,这将对应于四天的系列:
array_agg | name -----------------+------------------- {NULL,0,7,NULL} | A Place {NULL,1,NULL,2} | Some other place {5,NULL,3,NULL} | Yet another
我可以很容易地做到这一点,方法是CROSS JOIN确定一个日期范围和地点,并将其与计数结合起来:
CROSS JOIN
SELECT array_agg(counts.count), places.name FROM generate_series('2018-11-01', '2018-11-04', interval '1 days') as day CROSS JOIN places LEFT OUTER JOIN counts on counts.day = day.day AND counts.PlaceID = places.id GROUP BY places.name;
我似乎无法弄清楚的是如何让SQLAlchemy做到这一点。经过大量的挖掘,我发现了一个旧的Google网上论坛线程,该线程几乎可以正常工作,导致了这一点:
date_list = select([column('generate_series')])\ .select_from(func.generate_series(backthen, today, '1 day'))\ .alias('date_list') time_series = db.session.query(Place.name, func.array_agg(Count.count))\ .select_from(date_list)\ .outerjoin(Count, (Count.day == date_list.c.generate_series) & (Count.placeID == Place.id ))\ .group_by(Place.name)
这会为时间序列创建子选择,但会产生数据库错误:
有一个表“ places”的条目,但是不能在查询的这一部分中引用它。
所以我的问题是:您将如何在sqlalchemy中做到这一点。另外,我很容易接受这样的想法,因为我使用SQL的方法是头脑僵硬的。
问题在于,给定查询构造SQLAlchemy会按照以下方式生成查询:
SELECT ... FROM places, (...) AS date_list LEFT OUTER JOIN count ON ... AND count."placeID" = places.id ...
有2个FROM-list项:places和联接。项不能交叉引用相互1,因此,误差是由于places.id在ON-clause。
FROM
places
places.id
ON
SQLAlchemy不支持显式CROSS JOIN,但另一方面,aCROSS JOIN等效于INNER JOIN ON (TRUE)。您还可以省略将函数表达式包装在子查询中,并通过给它一个别名来按原样使用它:
INNER JOIN ON (TRUE)
date_list = func.generate_series(backthen, today, '1 day').alias('gen_day') time_series = session.query(Place.name, func.array_agg(Count.count))\ .join(date_list, true())\ .outerjoin(Count, (Count.day == column('gen_day')) & (Count.placeID == Place.id ))\ .group_by(Place.name)
1:函数调用项FROM除外,或使用LATERAL。
LATERAL