Úvod do SQLAlchemy ORM 

Co je to SQLAlchemy?

  • sada nástrojů pro práci s SQL databázemi
  • MySQL
  • PostgreSQL
  • Oracle
  • MSSQL
  • SQLite
  • ...
  • jednotné API
  • součástí je ORM framework

Co je to ORM?

  • Object Relational Mapping

SQLAlchemy

  • podpora různých dialektů (DBAPIs)
from sqlalchemy import create_engine

# vytvoření enginu pro SQLite
engine = create_engine('sqlite:///:memory:')

# vytvoření enginu pro MySQL
engine = create_engine(
    'mysql://scott:tiger@localhost/test',
    isolation_level='READ UNCOMMITTED',  # úroveň izolace
)

# vytvoření enginu pro PostgreSQL
engine = create_engine('postgresql+pg8000://scott:tiger@localhost/test')

SQLAlchemy CORE

  • sada nástrojů pro práci s databází bez ORM
# katalog všech definovaných tabulek
metadata = MetaData()

# definice tabulky users
users = Table('users', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String),
    Column('fullname', String),
)

# vytvoření tabulek (CREATE TABLE ...)
metadata.create_all(engine)

# otevření nového spojení spojení k DB
conn = engine.connect()

# vytvoření nového uživatele
conn.execute(users.insert().values(name='jan', fullname='Jan Novak'))

SQLAlchemy ORM

  • sada nástrojů pro práci s databází s ORM
# katalog všech definovaných tabulek
Base = declarative_base()

# definice modelu users (dědí Base)
class User(Base):
    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)

# vytvoření tabulek (CREATE TABLE ...)
Base.metadata.create_all(engine)

# vytvoření modelu uživatele
user = User(name='jan', fullname='Jan Novak')

# přidání do session (další slide) a commit
session.add(user)
session.commit(user)

SQLAlchemy Session

  • udržuje modely synchronizované s DB
  • spousta magie  spoustu problémů, které se špatně debugují
# příprava továrny
session_factory = sessionmaker(bind=engine)

# vytvoření nové session
session = session_factory()

# scoped session!
session = scoped_session(session_factory)

Příklady dotazů

  • WHERE name='prema'
session.query(User).filter(User.name == 'prema')

Příklady dotazů

  • WHERE name='prema'
session.query(User).filter(User.name == 'prema')
  • WHERE fullname NOT LIKE '%kar%'
session.query(User).filter(~User.name.like('%kar%')

Příklady dotazů

  • WHERE name='prema'
session.query(User).filter(User.name == 'prema')
  • WHERE fullname NOT LIKE '%kar%'
session.query(User).filter(~User.name.like('%kar%')
  • GROUP BY user_id HAVING count(*) > 4
session.query(Eshop).group_by(Eshop.user_id).having(func.count('*') > 4))

 Vykonání SELECT dotazu

  • načtení všech objektů do paměti
session.query(User).all()

 Vykonání SELECT dotazu

  • načtení všech objektů do paměti
session.query(User).all()
  • načtení prvního objektu
session.query(User).first()
session.query(User).get(24)  # podle primárního klíče

 Vykonání SELECT dotazu

  • načtení všech objektů do paměti
session.query(User).all()
  • načtení prvního objektu
session.query(User).first()
session.query(User).get(24)  # podle primárního klíče
  • iterace ve for-cyklu
for user in session.query(User):
    print(user.name)

UPDATE a DELETE

  • aktualizace
# s načtením celého objektu do paměti
user.name = 'mergado'
session.add(user)
session.commit()

# bez načtení do paměti
session.query(User).filter(User.id == 23).update({User.name: 'mergado'})

UPDATE a DELETE

  • aktualizace
# s načtením celého objektu do paměti
user.name = 'mergado'
session.add(user)
session.commit()

# bez načtení do paměti
session.query(User).filter(User.id == 23).update({User.name: 'mergado'})
  • odstranění
# s načtením celého objektu do paměti
session.delete(user)
session.commit()

# bez načtení do paměti
session.query(User).filter(User.id == 23).delete()

Spojování tabulek

  • definice pomocí relationship

class User(Base):
    # ...
    eshops = relationship(Eshop)  # one to many

user = sessinon.query(User).get(2)
for eshop in user.eshops:
    print(eshop.name)

Spojování tabulek

  • definice pomocí relationship

class User(Base):
    # ...
    eshops = relationship(Eshop)  # one to many

user = sessinon.query(User).get(2)
for eshop in user.eshops:
    print(eshop.name)
  • explicitní JOIN
# SELECT user.* FROM user INNER JOIN eshop ...
session.query(User).join(Eshop).filter(Eshop.name = 'Mironet.cz')

# SELECT project.*, log.* FROM project INNER JOIN log ...
session.query(Project, Log).join(Log, Project.id == Log.wtf_id)

# SELECT project.* FROM project LEFT OUTER JOIN log ...
session.query(Project).outerjoin(Log, Project.id == Log.wtf_id)

V SA ORM jde všechno!

  • UNION, INTERSECT, WITH, ...

  • subqueries

  • pracnější je CREATE VIEW

V SA ORM jde všechno!

query = session.query(all_columns())  # vznikne KeyedTuple
for Model in models:
    columns = get_columns(Model)
    query = query.outerjoin(
        Model,
        pg.and_(cls.set_id == Model.set_id,
                cls.item_id == Model.item_id)
    ).add_columns(*columns)
return query
  • UNION, INTERSECT, WITH, ...

  • subqueries

  • pracnější je CREATE VIEW

  • spojení tabulek ve for-cyklu:

... až na FULL JOIN

  • Chris Whithers:
    "This feels like a newbie question, but how would I do a full outer join in SQLAlchemy?"

... až na FULL JOIN

  • Chris Whithers:
    "This feels like a newbie question, but how would I do a full outer join in SQLAlchemy?"
  • Michael Bayer (autor SQLAlchemy):
    "I have a similar question - why?"

... až na FULL JOIN

  • Chris Whithers:
    "This feels like a newbie question, but how would I do a full outer join in SQLAlchemy?"
  • Michael Bayer (autor SQLAlchemy):
    "I have a similar question - why?"
  • Chris Whithers:
    "I have a self join [...]"

... až na FULL JOIN

  • Chris Whithers:
    "This feels like a newbie question, but how would I do a full outer join in SQLAlchemy?"
  • Michael Bayer (autor SQLAlchemy):
    "I have a similar question - why?"
  • Chris Whithers:
    "I have a self join [...]"
  • Michael Bayer:
    "[...] full outer join isn’t in the library but you can just subclass Join and do a @compiles to get that exact syntax."
  • :-)

Záludnosti

  • nezapomínat uzavírat session - v Postgresu pak uváznou dotazy na "IDLE in transaction"

Záludnosti

  • nezapomínat uzavírat session - v Postgresu pak uváznou dotazy na "IDLE in transaction"
  • stejný objekt nesmí být ve dvou session naráz -
    InvalidRequestError: Object '<Project at 0x7f13d4260b90>' is already attached to session '1' (this is '2')

Záludnosti

  • nezapomínat uzavírat session - v Postgresu pak uváznou dotazy na "IDLE in transaction"
  • stejný objekt nesmí být ve dvou session naráz -
    InvalidRequestError: Object '<Project at 0x7f13d4260b90>' is already attached to session '1' (this is '2')
  • expire_on_commit=True - jakmile změny zapíšete, označí se všechny objekty jako "expired", příštím přístupem k vlastnosti jiného objektu (např. user2.name) se nejdříve zavolá SELECT ...

Twitter/GitHub: @paveldedik

Úvod do SQLAlchemy ORM

By Pavel Dedík

Úvod do SQLAlchemy ORM

  • 51