This post is about SQLAlchemy and working with Object Relational Mappers (ORMs).
All of engines, connections, and sessions have a .execute
method. The results are all the same for simple select queries, but can vary as you do more complex operations.
Table of Contents
from sqlalchemy import create_engine, inspect
from sqlalchemy.sql import text
Engines
An engine is how SQLAlchemy communicates with a database.
Creating an Engine
It’s of the form: engine = create_engine('postgresql://<user>:<password>@<hostname>:<port (optional)>/<database_name>')
It might look like this:
engine = create_engine('postgresql://julius:post123@localhost:5432/postgres')
Exploring the Database with an Engine
You can use inspect
to learn more about your database.
inspector = inspect(engine)
inspector.get_table_names()
['customers', 'spatial_ref_sys', 'geom_table']
columns = inspector.get_columns('customers')
columns
[{'name': 'name',
'type': TEXT(),
'nullable': True,
'default': None,
'autoincrement': False,
'comment': None},
{'name': 'email',
'type': TEXT(),
'nullable': True,
'default': None,
'autoincrement': False,
'comment': None},
{'name': 'entry_num',
'type': INTEGER(),
'nullable': True,
'default': None,
'autoincrement': False,
'comment': None},
{'name': 'age',
'type': INTEGER(),
'nullable': True,
'default': None,
'autoincrement': False,
'comment': None}]
[c['name'] for c in columns]
['name', 'email', 'entry_num', 'age']
Raw Queries with an Engine
Now let’s start querying
query_string = 'select * from customers'
response = engine.execute(query_string)
You get a LegacyCursorResult
object as a response.
response
<sqlalchemy.engine.cursor.LegacyCursorResult at 0x20065e95160>
result = response.fetchall()
result
[('John Smith', 'js@gmail.com', 100, 25),
('Karen Smith', 'ks@gmail.com', 101, 42),
('John Smith', 'js@gmail.com', 102, 25)]
You can also use a TextClause
statement = text("""select * from customers""")
statement
<sqlalchemy.sql.elements.TextClause object at 0x0000020064993D00>
You can see what the query is by printing the statement.
print(statement)
select * from customers
response = engine.execute(statement)
result = response.fetchall()
result
[('John Smith', 'js@gmail.com', 100, 25),
('Karen Smith', 'ks@gmail.com', 101, 42),
('John Smith', 'js@gmail.com', 102, 25)]
ORMs
You can also use an ORM.
from geoalchemy2 import Geometry
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import INTEGER
from sqlalchemy.orm import declarative_base, sessionmaker
Base = declarative_base()
class GeomTable(Base):
__tablename__ = "geom_table"
id = Column("id", INTEGER, primary_key=True)
polygon_geom = Column("polygon_geom", Geometry("POLYGON", srid=4326))
geometry_geom = Column("geometry_geom", Geometry("GEMOETRY", srid=4326))
When you’re working with ORMs, you generally want to work with Sessions instead of engines.
Create your engine as before.
engine = create_engine('postgresql://julius:post123@localhost:5432/postgres')
Then create a Session.
Session = sessionmaker(engine)
session = Session()
Retrieving a Record
ORMs can be used to retrieve records.
response = session.query(GeomTable).filter_by(id=2)
response
<sqlalchemy.orm.query.Query at 0x20068f395b0>
To get the record, you could do .first()
, .get(2)
, .all()
, or .one()
.all()
returns a list
response.all()
[<__main__.GeomTable at 0x20068f75ca0>]
.distinct()
returns another query, so you would need to call .all()
on it.
response.distinct()
<sqlalchemy.orm.query.Query at 0x20068f75a30>
.first()
returns a single value (the first one), not in a list
response.first()
<__main__.GeomTable at 0x20068f75ca0>
result = response.first()
result
<__main__.GeomTable at 0x20068f75ca0>
result.__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState at 0x20068f75c70>,
'geometry_geom': <WKBElement at 0x20068f75be0; 0103000020e61000000100000005000000ef30a5de4b855ec0c3308f769ab347406ed22f8149855ec0dd60a6d99ab347404ab842264a855ec035c13c23a2b34740cd16b8834c855ec01b9125c0a1b34740ef30a5de4b855ec0c3308f769ab34740>,
'polygon_geom': <WKBElement at 0x20068f75c40; 0103000020e61000000100000005000000ef30a5de4b855ec0c3308f769ab347406ed22f8149855ec0dd60a6d99ab347404ab842264a855ec035c13c23a2b34740cd16b8834c855ec01b9125c0a1b34740ef30a5de4b855ec0c3308f769ab34740>,
'id': 2}
There’s also a good SO post on using filter
or filter_by
.
Sessions and raw text
Just because you’re using a session doesn’t mean you can’t use raw text. It’s quite easy.
query_string = text('select * from geom_table where id = 3')
with Session() as session:
record = session.query(query_string)
response = engine.execute(query_string)
result = response.fetchall()
result
[(3, '0103000020E610000001000000050000000000000000001040000000000000144000000000000014400000000000001440000000000000144000000000000010400000000000001040000000000000104000000000000010400000000000001440', '0103000020E610000001000000050000000000000000001040000000000000144000000000000014400000000000001440000000000000144000000000000010400000000000001040000000000000104000000000000010400000000000001440')]
Usually you’ll want it in wkt format.
query_string = 'select st_astext(polygon_geom) from geom_table where id = 3'
response = engine.execute(query_string)
result = response.fetchall()
result
[('POLYGON((4 5,5 5,5 4,4 4,4 5))',)]
If you want the SRID as well, you’ll need to use st_asewkt
.
sess.query
vs sess.execute
Creating a Record
gt = GeomTable()
gt.polygon_geom = 'POLYGON ((4 5, 5 5, 5 4, 4 4, 4 5))'
gt.geometry_geom = 'POLYGON ((4 5, 5 5, 5 4, 4 4, 4 5))'
with Session() as session:
session.add(gt)
session.commit()
Connections
You can also use a connection to execute a SQL query. Using a connection allows us to create transactions where either all the commands or, if there’s an error, roll it all back.
You can also make a connection:
conn = engine.connect()
conn.execute(...)
trans = conn.begin()
conn.execute('INSERT INTO MY_TABLE ...)
trans.commit()
with engine.connect() as con:
response = con.execute(statement)
with engine.connect() as con:
response = con.execute("select * from customers")
response
<sqlalchemy.engine.cursor.LegacyCursorResult at 0x20068fa2e50>
result = response.fetchall()
result
[('John Smith', 'js@gmail.com', 100, 25),
('Karen Smith', 'ks@gmail.com', 101, 42),
('John Smith', 'js@gmail.com', 102, 25)]
Updating Databases
If you’re using an ORM and want to update an object, you’ll have to get the record, then update it and run merge
.
with session(engine=engine) as sess:
records = sess.query(MyTable).filter_by(id = 12345)
rec = records.first()
rec.my_column = 'new_value'
with session(engine=engine)) as sess:
sess.merge(rec)
try:
sess.commit()
except Exception as ex:
print(ex)
sess.rollback()
Or you could pass one sess to the whole thing.
Synactic Sugar and Tricks
Get the date from a timestamp
date(my_timestamp)
Optimizing
You can learn how your query is acting by using the EXPLAIN
command. For example, if you want to know what index is used in a query, you can do this:
explain select * from customers
You might get a response that looks like
Index Scan using customers_pkey on customers (cost=0.43..8.45 rows=1 width=285)
You can also use analyze
. This will actually run the command and analyze the runtime statistics.
explain analyze select * from customers