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