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