2017-04-12 11 views
4

sqlalchemypostgresql DB)には、より良い用語がないため、境界付き合計関数を作成したいと考えています。目標は、定義された範囲内で実行中の合計を作成することです。境界があるSQLAlchemyの合計関数

現在のところ、範囲外の合計を計算するのに効果的なものがあります。このような何か:

from sqlalchemy.sql import func 

foos = (
    db.query(
     Foo.id, 
     Foo.points, 
     Foo.timestamp, 
     func.sum(Foo.points).over(order_by=Foo.timestamp).label('running_total') 
    ) 
    .filter(...) 
    .all() 
) 

しかし、私はこの実行中の合計が常に特定の範囲内にあることをバインドできるようにしたいと思い、の[-100, 100]を言わせて。 (running_totalを参照)だから我々はこのようなものになるだろう:

{'timestamp': 1, 'points': 75, 'running_total': 75} 
{'timestamp': 2, 'points': 50, 'running_total': 100} 
{'timestamp': 3, 'points': -100, 'running_total': 0} 
{'timestamp': 4, 'points': -50, 'running_total': -50} 
{'timestamp': 5, 'points': -75, 'running_total': -100} 

任意のアイデア?生のSQLで

、あなたがgreatest & least functionsを使用してこれを行うだろう:

答えて

2

ノート私の最初の答えは下の編集を参照して、間違っています。

このような何か:

LEAST(GREATEST(SUM(myfield) OVER (window_clause), lower_bound), upper_bound) 

SQLAlchemyの表現言語は、ユーザー@pozsがコメントで指摘するように、上記生成されませんほとんど同じ

1 2つの書き込み
import sqlalchemy as sa 
import sqlalchemy.ext.declarative as dec 
base = dec.declarative_base() 

class Foo(base): 
    __tablename__ = 'foo' 
    id = sa.Column(sa.Integer, primary_key=True) 
    points = sa.Column(sa.Integer, nullable=False) 
    timestamp = sa.Column('tstamp', sa.Integer) 

upper_, lower_ = 100, -100 
win_expr = func.sum(Foo.points).over(order_by=Foo.timestamp) 
bound_expr = sa.func.least(sa.func.greatest(win_expr, lower_), upper_).label('bounded_running_total') 

stmt = sa.select([Foo.id, Foo.points, Foo.timestamp, bound_expr]) 

str(stmt) 
# prints output: 
# SELECT foo.id, foo.points, foo.tstamp, least(greatest(sum(foo.points) OVER (ORDER BY foo.tstamp), :greatest_1), :least_1) AS bounded_running_total 
# FROM foo' 


# alternatively using session.query you can also fetch results 

from sqlalchemy.orm sessionmaker 
DB = sessionmaker() 
db = DB() 
foos_stmt = dm.query(Foo.id, Foo.points, Foo.timestamp, bound_expr).filter(...) 
str(foos_stmt) 
# prints output: 
# SELECT foo.id, foo.points, foo.tstamp, least(greatest(sum(foo.points) OVER (ORDER BY foo.tstamp), :greatest_1), :least_1) AS bounded_running_total 
# FROM foo' 

foos = foos_stmt.all() 

EDITすることができます意図した結果。

@pozsでは、2つの代替アプローチが提示されています。ここでは、sqlalchemyを介して構築された最初の再帰的クエリ・アプローチを採用しました。

import sqlalchemy as sa 
import sqlalchemy.ext.declarative as dec 
import sqlalchemy.orm as orm 
base = dec.declarative_base() 

class Foo(base): 
    __tablename__ = 'foo' 
    id = sa.Column(sa.Integer, primary_key=True) 
    points = sa.Column(sa.Integer, nullable=False) 
    timestamp = sa.Column('tstamp', sa.Integer) 

upper_, lower_ = 100, -100 
t = sa.select([ 
    Foo.timestamp, 
    Foo.points, 
    Foo.points.label('bounded_running_sum') 
]).order_by(Foo.timestamp).limit(1).cte('t', recursive=True) 

t_aliased = orm.aliased(t, name='ta') 

bounded_sum = t.union_all(
    sa.select([ 
    Foo.timestamp, 
    Foo.points, 
    sa.func.greatest(sa.func.least(Foo.points + t_aliased.c.bounded_running_sum, upper_), lower_) 
    ]).order_by(Foo.timestamp).limit(1) 
) 
stmt = sa.select([bounded_sum]) 

# inspect the query: 
from sqlalchemy.dialects import postgresql 
print(stmt.compile(dialect=postgresql.dialect(), 
        compile_kwargs={'literal_binds': True})) 
# prints output: 
# WITH RECURSIVE t(tstamp, points, bounded_running_sum) AS 
# ((SELECT foo.tstamp, foo.points, foo.points AS bounded_running_sum 
# FROM foo ORDER BY foo.tstamp 
# LIMIT 1) UNION ALL (SELECT foo.tstamp, foo.points, greatest(least(foo.points + ta.bounded_running_sum, 100), -100) AS greatest_1 
# FROM foo, t AS ta ORDER BY foo.tstamp 
# LIMIT 1)) 
# SELECT t.tstamp, t.points, t.bounded_running_sum 
# FROM t 

Iはまた、1つは、これが生成する純粋SQLAlchemyの方法であろう再帰CTEを

で動作する代わりに、セッションを使用することができる方法を強調しており、上記を構築するための基準としてこのlink from the documentationを使用必要な結果。

@pozsが示唆している第2のアプローチは、sqlalchemyを介して使用することもできます。

ソリューションは、このsection from the documentation

+2

これは、あなたが正しい、必要な結果に – pozs

+0

@pozsを生成しません。私は答えを削除するか、歴史的な理由でそれを保持する必要がありますか? –

+0

または修正することができます。あなたがSQLAlchemyでより良く/異なって/それを行う方法を知っているなら、私も興味があります。 – pozs

5

の変種でなければならないであろう残念ながら、組み込みの集計では、ウィンドウ関数呼び出しを使用して予想される出力を達成するのを助けることはできません。

は手動でrecursive CTEと行に一つ一つを計算すると期待される出力を得ることができます:

with recursive t as (
    (select *, points running_total 
    from  foo 
    order by timestamp 
    limit 1) 
    union all 
    (select foo.*, least(greatest(t.running_total + foo.points, -100), 100) 
    from  foo, t 
    where foo.timestamp > t.timestamp 
    order by foo.timestamp 
    limit 1) 
) 
select timestamp, 
     points, 
     running_total 
from t; 

残念ながら、これはSQLAlchemyのでは達成するのは非常に難しいでしょう。

select timestamp, 
     points, 
     bounded_sum(points, -100.0, 100.0) over (order by timestamp) running_total 
from foo; 
:、あなただけの bounded_sumへの呼び出しであることを sumにお電話を交換する必要がある。これにより

create function bounded_add(int_state anyelement, next_value anyelement, next_min anyelement, next_max anyelement) 
    returns anyelement 
    immutable 
    language sql 
as $func$ 
    select least(greatest(int_state + next_value, next_min), next_max); 
$func$; 

create aggregate bounded_sum(next_value anyelement, next_min anyelement, next_max anyelement) 
(
    sfunc = bounded_add, 
    stype = anyelement, 
    initcond = '0' 
); 

あなたの他のオプションは次のように、特定のニーズに合わせてwrite a custom aggregateに、あります

この後者のソリューションはおそらくさらに拡張されます。

http://rextester.com/LKCUK93113

関連する問題