2017-03-02 7 views
1

SQLAlchemyでテーブルをフェッチして、指定したdateTimeから最も近いdatetimeを見つける簡単な(高速な)方法はありますか?ほとんどの場合、デルタは指定された日時とテーブルの日時の間の秒数になります。最初の後を取得するためにSQLAlchemyに最も近い日時

select * from table where datetime > your_date_time limit 1; 
select * from table where datetime < your_date_time limit 1; 

と:私はあなたのような何かを行うことができ、プレーンSQLでSQLiteの

答えて

3

を使用して、同様の何かを書くことができますSQLAlchemyので

は簡単な解決策が、最速ではないかもしれません。クイックで汚れた方法は、与えられた日時よりも大きく、より小さい和集合をフェッチし、昇順と降順にソートして1行目に限定してから、より小さい値のものを選択します。

sopython=> create table testi (key timestamp without time zone primary key); 
CREATE TABLE 
sopython=> insert into testi 
select * 
from generate_series(now() at time zone 'utc' - '1 year'::interval, 
        now() at time zone 'utc', 
        '12 seconds'::interval); 
INSERT 0 2628001 

とPython:12Sの分解能でタイムスタンプの年分とバックエンドとテストテーブルとしてPostgreSQLの使用例

In [29]: from sqlalchemy import union_all, case 

In [30]: from sqlalchemy.orm import aliased 

In [31]: the_time = datetime(2016, 5, 5, 10, 45, 55) 

はでサブクエリをラップする、最も近い値をフェッチ組合を作成しますSELECTステートメントは、たとえば、SQLiteでも動作します。

In [32]: greater = session.query(Testi).filter(Testi.key > the_time).\ 
    ...:  order_by(Testi.key.asc()).limit(1).subquery().select() 

In [33]: lesser = session.query(Testi).filter(Testi.key <= the_time).\ 
    ...:  order_by(Testi.key.desc()).limit(1).subquery().select() 

In [34]: the_union = union_all(lesser, greater).alias() 

エイリアス組合

In [35]: testi_alias = aliased(Testi, the_union) 

の結果にモデル所定の日時

In [36]: the_diff = testi_alias.key - the_time 

又はフェッチ

In [36]: the_diff = func.julianday(testi_alias.key) - func.julianday(the_time) 

のSQLiteでの差を算出近いです2のその奇妙さは、Postgresqlの間隔のうちgetting the absolute valueです。他のDBでは、差異計算と絶対値の取得には異なる解決策が必要です。 SQLiteは単にfunc.abs(the_diff)です。単に差分により発注、制限の簡単な解決策は、このマシン上でいくつか800msで走っている間

In [37]: session.query(testi_alias).\ 
    ...:  order_by(case([(the_diff < timedelta(0), -the_diff)], 
    ...:     else_=the_diff)).\ 
    ...:  first() 
Out[37]: <sqlalchemy.ext.automap.testi at 0x7f096f837828> 

In [38]: _.key 
Out[38]: datetime.datetime(2016, 5, 5, 10, 45, 54, 855799) 

は、上記のクエリを約70-100msで終了します。データを2倍にすると、seqスキャンに依存する単純なソリューションも同様に倍増します。

組合がテーブルからこれらの2つの値を検索します:

In [14]: session.query(testi_alias.key).all() 
Out[14]: 
[(datetime.datetime(2016, 5, 5, 10, 45, 54, 855799)), 
(datetime.datetime(2016, 5, 5, 10, 46, 6, 855799))] 

そして最後に、あなたは一般的な機能でそれをすべてを包むことができます。

def get_closest(session, cls, col, the_time): 
    greater = session.query(cls).filter(col > the_time).\ 
     order_by(col.asc()).limit(1).subquery().select() 

    lesser = session.query(cls).filter(col <= the_time).\ 
     order_by(col.desc()).limit(1).subquery().select() 

    the_union = union_all(lesser, greater).alias() 
    the_alias = aliased(cls, the_union) 
    the_diff = getattr(the_alias, col.name) - the_time 
    abs_diff = case([(the_diff < timedelta(0), -the_diff)], 
        else_=the_diff) 

    return session.query(the_alias).\ 
     order_by(abs_diff.asc()).\ 
     first() 

get_closest(session, Testi, Testi.key, the_time) 
+0

select * from tableを使用していますか?datetime> your_date_time limit 1; select * from tableここで、datetime Timo

+0

私は行または時間の値を取得できますか? – Timo

+0

2つの選択肢はソリューションの背後にあるアイデアですが、完全なソリューションではありません。最小の差が0であれば、特定のクエリは正しい結果を返しません。どちらのクエリも等価性が含まれていないためです。いずれかを返すようにクエリを適合させることができます。 –

0

を使用してい :

日付列は、主キー

EDITですまずあなたの日時の前に、次に差を計算し、最も近いものを取得します。あなたは、おそらくそれは、単純な主キーであるので、「差分昇順で並べ替え、1行目をフェッチ」.limitまたは.filter方法

+0

私が最も近い時間に一致する行を必要とします – Timo

+0

'select *'は時刻だけでなく、完全な行を返します。 – Gianluca

関連する問題