我正在使用 SQLAlchemy 作为 python 项目的 ORM。我创建了一些模型/架构,并且运行良好。现在我需要查询现有的 MySQL 数据库,不需要插入/更新,只需 select 语句。
如何围绕现有数据库的表创建包装器?我已经简要浏览了 sqlalchemy 文档和 SO,但找不到任何相关内容。所有建议执行方法,我需要编写原始 sql 查询,而我想以与 SA 模型相同的方式使用 SQLAlchemy 查询方法。
例如,如果现有数据库有表名 User 那么我想使用 dbsession 查询它(仅选择操作,可能带有 join)
您似乎有这样的印象:SQLAlchemy 只能使用 SQLAlchemy 创建的数据库结构(可能使用
MetaData.create_all()
) - 这是不正确的。 SQLAlchemy 可以与预先存在的数据库完美配合,您只需定义模型以匹配数据库表即可。一种方法是使用反射,正如 Ilja Everilä 建议的那样:
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class MyClass(Base):
__table__ = Table('mytable', Base.metadata,
autoload=True, autoload_with=some_engine)
(在我看来,这对于一次性脚本来说完全没问题,但如果数据库结构可能随着时间的推移而改变,则可能会在“真实”应用程序中导致令人难以置信的令人沮丧的错误)
另一种方法是像往常一样简单地定义模型,注意定义模型以匹配数据库表,这并不困难。这种方法的好处是,您可以仅将数据库表的子集映射到模型,甚至可以仅将表列的子集映射到模型的字段。假设数据库中有 10 个表,但只对
users
表感兴趣,您只需要 id
、name
和 email
字段:
import sqlalchemy as sa
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class User(Base):
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
email = sa.Column(sa.String)
(注意我们不需要定义一些只需要发出正确的 DDL 的细节,例如字符串字段的长度或
email
字段有一个索引)
SQLAlchemy 不会发出 INSERT/UPDATE 查询,除非您在代码中创建或修改模型。如果您想确保查询是只读的,您可以在数据库中创建一个特殊用户并仅授予该用户 SELECT 权限。或者/此外,您还可以尝试在应用程序代码中回滚事务。
您可以使用 automap 扩展访问现有表:
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
Base = automap_base()
Base.prepare(engine, reflect=True)
Users = Base.classes.users
session = Session(engine)
res = session.query(Users).first()
创建一个启用自动加载的表来检查它。一些示例代码:
from sqlalchemy.sql import select
from sqlalchemy import create_engine, MetaData, Table
CONN_STR = '…'
engine = create_engine(CONN_STR, echo=True)
metadata = MetaData()
cookies = Table(
'cookies', metadata, autoload=True, autoload_with=engine
)
cols = cookies.c
with engine.connect() as conn:
query = (
select([cols.created_at, cols.name])
.order_by(cols.created_at)
.limit(1)
)
for row in conn.execute(query):
print(row)
其他答案没有提到如果您有一个没有主键的表该怎么办,所以我想我会解决这个问题。假设有一个名为 Customers 的表,其中包含 CustomerId、CustomerName、CustomerLocation 列,您可以这样做;
from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine, MetaData, Column, String, Table
from sqlalchemy.orm import Session
Base = automap_base()
conn_str = '...'
engine = create_engine(conn_str)
metadata = MetaData()
# you only need to define which column is the primary key. It can automap the rest of the columns.
customers = Table('Customers',metadata, Column('CustomerId', String, primary_key=true), autoload=True, autoload_with=engine)
Base.prepare()
Customers= Base.classes.Customers
session = Session(engine)
customer1 = session.query(Customers).first()
print(customer1.CustomerName)
假设我们有一个名为
accounts
的 Postgresql 数据库。我们已经有一个名为 users
的表。
import sqlalchemy as sa
psw = "verysecret"
db = "accounts"
# create an engine
pengine = sa.create_engine('postgresql+psycopg2://postgres:' + psw +'@localhost/' + db)
from sqlalchemy.ext.declarative import declarative_base
# define declarative base
Base = declarative_base()
# reflect current database engine to metadata
metadata = sa.MetaData(pengine)
metadata.reflect()
# build your User class on existing `users` table
class User(Base):
__table__ = sa.Table("users", metadata)
# call the session maker factory
Session = sa.orm.sessionmaker(pengine)
session = Session()
# filter a record
session.query(User).filter(User.id==1).first()
警告:您的表应该定义主键。否则,Sqlalchemy 不会喜欢它。