2017-03-02 2 views
1

Ist ihre einfache (schnelle) Möglichkeit, eine Tabelle zu holen und die Datetime am nächsten von einem bestimmten dateTime in SQLAlchemy zu finden? In den meisten Fällen ist das Delta Sekunden zwischen der angegebenen Datumszeit und der in der Tabelle angegebenen Zeit.SQLAlchemy nächste Datetime

Die Datumsspalte ist der Primärschlüssel

EDIT: I SQLite

Antwort

3

Da der Primärschlüssel ist ein einfaches „Sortieren nach diff aufsteigend, holen 1. Reihe“ ist vielleicht nicht die schnellste sein, obwohl einfache Lösung. Eine schnelle und schmutzige Art und Weise könnte eine Vereinigung von größer als und kleiner als die gegebene Datetime, sortiert auf- und absteigenden und beschränkt sich auf der 1. Reihe, zu holen und dann mit dem kleineren diff vom 2.

auf einen Blick Hier ein Beispiel unter Verwendung von Postgresql als Backend und eine Testtabelle mit einem Jahr im wert von Zeitstempel mit 12s Auflösung:

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 

und dem Python:

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) 

die Vereinigung erstellen, die am nächsten wert abruft, Umwickeln die Unterabfragen in eine SELECT-Anweisung, so dass es funktioniert auch in SQLite zum Beispiel.

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

Alias ​​das Modell auf dem Ergebnis der Vereinigung

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

Berechnen der Differenz aus dem vorgegebenen Datumzeit

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

oder in SQLite

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

Fetch je näher der 2. Die ca Se Monstrosität ist für getting the absolute value des Intervalls, in Postgresql. Andere DBs erfordern unterschiedliche Lösungen für die Differenzberechnung und den Absolutwert. Mit SQLite einfach func.abs(the_diff).

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) 

Während die einfache Lösung nur durch diff Bestellung und in einigen 800ms auf dieser Maschine lief zu begrenzen, beendet die obige Abfrage in etwa 70-100ms. Wenn Sie die Daten verdoppeln, verdoppelt sich auch die einfache Lösung, die auf Seq-Scan basiert.

Die Vereinigung findet diese beiden Werte aus der Tabelle:

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

Und schließlich können Sie es wickeln alle in einer generischen Funktion auf:

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

Ist die Verwendung von Select * aus der Tabelle, wo datetime> your_date_time limit 1; Wählen Sie * aus der Tabelle where datetime Timo

+0

würde ich die Zeile oder den Zeitwert bekommen? – Timo

+0

Die 2 Auswahlen sind die Idee hinter der Lösung, aber nicht die komplette Lösung. Beachten Sie, dass die bestimmten Abfragen das korrekte Ergebnis nicht zurückgeben, wenn die kleinste Differenz 0 ist, da keine der beiden Abfragen die Gleichheit enthält. Sie können die Abfrage so anpassen, dass sie entweder eins zurückgibt. –

0

Mit einem normalen SQL bin mit Ihnen so etwas wie tun:

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

die erste nach erhalten und die zuerst vor Ihrer Datetime, dann berechnen Sie die Differenz und erhalten Sie die nächste.

Mit SQLAlchemy können Sie wahrscheinlich schreiben etwas ähnliches die .limit oder .filter Methode

+0

Ich brauche die Zeile, die die nächste Zeit passen nicht nur die Zeit – Timo

+0

'Select *' zurückgeben die komplette Zeile, nicht nur die Zeit – Gianluca