2017-09-25 1 views
3

Ich versuche, Composite-Spalten von Tiger.Geocode-Funktion in Python mithilfe von Sqlalchemy ziehen.
In reiner SQL-Form sieht das wie folgt aus:Zugriff auf Composite-Datentyp mit sqlalchemy in Postgres

SELECT 
    g.rating 
    ,ST_X(g.geomout) As lon 
    ,ST_Y(g.geomout) As lat 
    ,(addy).address As stno 
    ,(addy).streetname As street 
    ,(addy).streettypeabbrev As styp 
    ,(addy).location As city 
    ,(addy).stateabbrev As st 
    ,(addy).zip 
FROM geocode(pagc_normalize_address('1 Capitol Square Columbus OH 43215')) As g 
; 

Dies erzeugt die folgende Ausgabe:

# rating lon lat stno street styp city st zip 
1 17 -82.99782603089086 39.96172588526335 1 Capital St Columbus OH 43215 

Das Problem, das ich mit konfrontiert bin, wie Composite-Spalt verweisen, wenn das Objekt von sqlalchemy abfragt (rating, lon, lat, stno, straße, styp, stadt, st, zip)?

Bitte und Danke.

+2

Haben Sie sah [ 'sqlalchemy_utils.types.pg_composite'] (http: //sqlalchemy-utils.readthedocs.io/en/latest/data_types.html#module-sqlalchemy_utils.types.pg_composite)? – univerio

+0

Ich kann sehen, wie das in dem Fall funktionieren würde, wenn mein zusammengesetzter Typ eine Spalte in einer statischen Tabelle ist, aber in meinem Fall ist es ein zusammengesetzter Typ, der von einer Funktion zurückgegeben wird. In meinem Setup haben wir keine Sqlalchemy-Klassen für irgendwelche Funktionen (nur Tabellen) definiert. Irgendwelche Gedanken zu diesem Teil des Problems? – BrianEdwardHoover

Antwort

3

SQLAlchemy unterstützt Satz zurückgebende Funktionen nicht direkt, aber seine FunctionElement s werden als FromClause s betrachtet, was bedeutet, dass Sie sie bereits als Tabellen behandeln können; Wir müssen lediglich die Möglichkeit hinzufügen, eine bestimmte Spalte aus der Funktion auszuwählen. Glücklicherweise ist dies einfach (wenn auch nicht offensichtlich):

from sqlalchemy.sql.base import ColumnCollection 
from sqlalchemy.sql.expression import column 
from sqlalchemy.sql.functions import FunctionElement 

NormAddy = CompositeType(
    "norm_addy", 
    [ 
     Column("address", Integer), 
     Column("predirAbbrev", String), 
     Column("streetName", String), 
     Column("streetTypeAbbrev", String), 
     Column("postdirAbbrev", String), 
     Column("internal", String), 
     Column("location", String), 
     Column("stateAbbrev", String), 
     Column("zip", String), 
     Column("parsed", Boolean), 
    ], 
) 

class geocode(GenericFunction): 
    columns = ColumnCollection(
     Column("rating", Integer), 
     column("geomout"), # lowercase column because we don't have the `geometry` type 
     Column("addy", NormAddy), 
    ) 

Subclassing von GenericFunction hat den zusätzlichen Vorteil der Registrierung die geocode Funktion global, so dass func.geocode wird wie erwartet.

g = func.geocode(func.pagc_normalize_address("1 Capitol Square Columbus OH 43215")).alias("g") 
query = session.query(
    g.c.rating, 
    func.ST_X(g.c.geomout).label("lon"), 
    func.ST_Y(g.c.geomout).label("lat"), 
    g.c.addy.address.label("stno"), 
    g.c.addy.streetName.label("street"), 
    g.c.addy.streetTypeAbbrev.label("styp"), 
    g.c.addy.location.label("city"), 
    g.c.addy.stateAbbrev.label("st"), 
    g.c.addy.zip, 
).select_from(g) 

Leider funktioniert das nicht ganz. Es scheint einen Fehler zu geben, der die Syntax g.c.addy.address in den letzten Versionen von SQLAlchemy nicht funktioniert. Wir können es ganz schnell beheben (obwohl dies wirklich in sqlalchemy_utils festgelegt werden sollte):

from sqlalchemy_utils.types.pg_composite import CompositeElement 
import sqlalchemy_utils 

class CompositeType(sqlalchemy_utils.CompositeType): 
    class comparator_factory(_CompositeType.comparator_factory): 
     def __getattr__(self, key): 
      try: 
       type_ = self.type.typemap[key] 
      except KeyError: 
       raise AttributeError(key) 
      return CompositeElement(self.expr, key, type_) 

    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 
     self.typemap = {c.name: c.type for c in self.columns} 

Jetzt funktioniert es:

print(query.statement.compile(engine)) 
# SELECT g.rating, ST_X(g.geomout) AS lon, ST_Y(g.geomout) AS lat, (g.addy).address AS stno, (g.addy).streetName AS street, (g.addy).streetTypeAbbrev AS styp, (g.addy).location AS city, (g.addy).stateAbbrev AS st, (g.addy).zip AS zip_1 
# FROM geocode(pagc_normalize_address(%(pagc_normalize_address_1)s)) AS g 
Verwandte Themen