postgresql 实践
pydantic 实践
SQLAlchemy 是一个 ORM 框架。SQLAlchemy 是一个用于 Python 的 SQL 工具和对象关系映射(ORM)库。它允许你通过 Python 代码来与关系型数据库交互,而不必直接编写SQL语句。
简单介绍一下对象关系映射吧,对象关系映射(英语:Object Relational Mapping,简称 ORM,或O/RM,或O/R mapping),是一种程序设计技术, 用于实现面向对象编程语言里不同类型系统的数据之间的转换。 从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。大白话:对象模型与数据库表的映射。
SQLAlchemy 是一个强大的 Python ORM 框架,主要应用于以下场景:
数据库访问和操作: SQLAlchemy 提供了高层抽象来操作数据库,可以避免写原生 SQL 语句。支持多种数据库后端(MySQL、MongoDB、SQLite、PostgreSQL)。
ORM映射: 建立 Python 类与数据库表的映射关系,简化数据模型的操作,支持声明式操作。
复杂查询: SQLAlchemy 提供丰富的查询方式,如过滤、分组、联结等,可以构建复杂查询。
异步查询: 基于Greenlet 等实现异步查询,提高查询效率。
事务控制: 通过 Session 管理数据库会话和事务。
工具集成: 如数据迁移工具 Alembic,可以实现 Schema 版本控制和迁移。
大数据集查询: 基于 Pagination 实现数据分页,避免大量数据查询内存溢出。
多数据库支持: 支持 Postgres、MySQL、Oracle 等主流数据库。
Web框架集成: 框架如 Flask 可以集成 SQLAlchemy,便于 Web 应用开发。
参考:
https://www.jb51.net/python/325524ud6.htm
https://blog.51cto.com/u_13019/12307379
在使用 SQLAlchemy 之前,首先需要安装它。可以使用以下命令使用 pip 安装:
pip install sqlalchemy
pip install pymysql # 安装 MySQL
sqlalchemy 可以操作多种数据库,需要注意的是,不同的数据库的连接方式是不一样,依赖的库也不一样,这里列举一些常见数据依赖和连接格式:
关系型数据库
数据库 | 依赖 | 连接字符串 |
---|---|---|
MySQL | pymysql | mysql+pymysql://username:password@localhost:3306/database_name |
PostgreSQL | psycopg2 | postgresql://username:password@localhost:5432/database_name |
SQLite | 不需要 | sqlite:///example.db |
Oracle | cx_Oracle | oracle://username:password@localhost:1521/orcl |
NoSQL数据库
数据库 | 依赖 | 连接字符串 |
---|---|---|
MongoDB | pymongo | mongodb://username:password@localhost:27017/database_name |
CouchDB | couchdb | couchdb://username:password@localhost:5984/database_name |
Redis | redis | redis://localhost:6379/0 |
from sqlalchemy import create_engine
dbHost = 'mysql+pymysql://root:[email protected]:3306/test'
engine = create_engine(
dbHost,
echo=True, # 是否打印SQL
pool_size=10, # 连接池的大小,指定同时在连接池中保持的数据库连接数,默认: 5
max_overflow=20, # 超出连接池大小的连接数,超过这个数量的连接将被丢弃,默认: 5
pool_pre_ping=True
)
注意: create_engine 函数在调用时并不会立即与数据库建立真实的连接。相反,它仅是为了创建一个数据库引擎对象,该对象封装了连接到数据库的配置和行为,但直到实际执行数据库操作时才会尝试建立连接。
常见参数说明:
echo: True/False,是否打印执行的SQL,默认False;
pool_size: 连接池的大小,指同时在连接池中保持的数据库连接数,默认为5;
max_overflow: 溢出连接的最大数量。当连接池达到上限后,新的连接请求将被放置在溢出队列中。如果溢出队列满了,将引发异常,设置值需要>=pool_size;
pool_recycle: 指定连接在连接池中保持的最长时间(以秒为单位)。当设置为非 None 时,连接将在此时间后被回收,避免数据库服务器断开空闲连接,默认为-1。
pool_pre_ping: 当设置为 True 时,在每次从连接池中取出(即“签出”或“checkout”)连接之前,先测试该连接是否仍然活跃(即“存活”)的功能。如果查询失败,说明连接已经断开或不再可用,连接池将自动丢弃该连接,并从池中获取一个新的连接(如果可用)或根据配置创建一个新的连接。
官网地址
使用 SQLAlchemy 连接到数据库,需要提供数据库的连接字符串,其中包含有关数据库类型、用户名、密码、主机和数据库名称的信息。
from sqlalchemy import create_engine
# 例如,连接到 SQLite 数据库
engine = create_engine('sqlite:///example.db')
# 例如,连接到 MySQL 数据库
username = 'your_mysql_username'
password = 'your_mysql_password'
host = 'your_mysql_host' # 例如:'localhost' 或 '127.0.0.1'
port = 'your_mysql_port' # 通常是 3306
database = 'your_database_name'
# 创建连接引擎
dbHost = f'mysql+pymysql://{username}:{password}@{host}:{port}/{database}'
engine = create_engine(
dbHost,
echo=True, # 是否打印SQL
pool_size=10, # 连接池的大小,指定同时在连接池中保持的数据库连接数,默认:5
max_overflow=20, # 超出连接池大小的连接数,超过这个数量的连接将被丢弃,默认: 5
)
sqlalchemy 中的 Column 类有很多参数,以下是一些常用的参数:
name (str): 列的名称。
type_ (TypeEngine): 列的数据类型,例如 String, Integer, DateTime 等。
primary_key (bool): 指定是否为主键列。
unique (bool): 指定是否唯一。
nullable (bool): 指定是否可以为空。
default: 在插入新记录时,如果没有提供该列的值,则将使用默认值。
server_default: 指定服务器端的默认值。
index (bool): 指定是否创建索引。
autoincrement (bool): 指定是否自增。
onupdate: 在更新时设置的值。
server_onupdate: 服务器端在更新时设置的值。
comment (str): 列的注释。
使用 SQLAlchemy 的 ORM 功能,可以定义 Python 类来映射数据库中的表。每个类对应数据库中的一张表,类的属性对应表中的列。
# 导入必要的模块
from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy.ext.declarative import declarative_base
# 创建一个基类,用于定义数据模型的基本结构
Base = declarative_base()
# 定义一个数据模型类,对应数据库中的 'users' 表
class User(Base):
# 定义表名
__tablename__ = 'users'
# 定义列:id,是整数类型,主键(primary_key=True),并使用 Sequence 生成唯一标识
id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
# 定义列:name,是字符串类型,最大长度为50
name = Column(String(50))
# 定义列:age,是整数类型
age = Column(Integer)
通过在代码中调用 create_all 方法,可以根据定义的模型创建数据库表。
Base.metadata.create_all(engine)
使用 SQLAlchemy 进行插入数据的操作,首先需要创建一个会话(Session)对象,然后使用该对象添加数据并提交。
# 导入创建会话的模块
from sqlalchemy.orm import sessionmaker
# 使用 sessionmaker 创建一个会话类 Session,并绑定到数据库引擎(bind=engine)
Session = sessionmaker(bind=engine)
# 创建一个实例化的会话对象 session
session = Session()
# 创建一个新的 User 实例,即要插入到数据库中的新用户
new_user = User(name='John Doe', age=30)
# 将新用户添加到会话中,即将其添加到数据库操作队列中
session.add(new_user)
# 提交会话,将所有在此会话中的数据库操作提交到数据库
session.commit()
如果添加过程中发生任何错误,我们将回滚事务,确保数据库的一致性。
try:
# 开始一个新的事务
session.begin()
# 创建新用户对象
user1 = User(name='Alice', email='[email protected]')
user2 = User(name='Bob', email='[email protected]')
# 添加到会话中
session.add(user1)
session.add(user2)
# 提交事务,将所有更改保存到数据库
session.commit()
print("Users added successfully.")
except Exception as e:
# 如果在添加用户过程中发生错误,则回滚事务
session.rollback()
print(f"An error occurred: {e}")
finally:
# 关闭会话
session.close()
在这个示例中,我们使用 session.begin() 显式地开始了一个新的事务。然后,我们尝试添加两个新用户到会话中。如果在这个过程中没有发生任何错误,我们使用 session.commit() 提交事务,将所有更改保存到数据库中。但是,如果在添加用户的过程中发生了任何异常(例如,由于重复的电子邮件地址或数据库连接问题),我们将使用 session.rollback() 回滚事务,确保数据库的一致性。
使用 SQLAlchemy 进行查询数据的操作,可以通过查询语句或使用 ORM 查询接口。
# 使用查询语句
result = engine.execute('SELECT * FROM users')
# 使用 ORM 查询接口
users = session.query(User).all()
query.first(): 返回查询结果的第一条记录,如果没有结果则返回 None。
query.one(): 返回查询结果的唯一一条记录,如果结果集为空或包含多条记录,则引发 sqlalchemy.exc.NoResultFound 或 sqlalchemy.exc.MultipleResultsFound 异常。
query.one_or_none(): 返回查询结果的唯一一条记录,如果结果集为空则返回 None,如果包含多条记录则引发 sqlalchemy.exc.MultipleResultsFound 异常。
query.scalar(): 返回查询结果的第一列的第一个值,通常用于获取单个聚合函数的结果,如 COUNT、SUM 等。
query.filter(): 添加过滤条件到查询中,可以通过链式调用添加多个条件。
query.limit(10): 限制查询结果的数量。
query.join(*props, **kwargs): 执行连接操作,可以连接其他表进行复杂的查询。
query.outerjoin(*props, **kwargs): 执行外连接操作,返回左表中的所有记录以及右表中匹配的记录。
query.distinct(): 去除查询结果中的重复记录。
query.count(): 返回查询结果的记录数量,通常与 filter 结合使用以实现条件查询的数量统计。
假设我们有两个模型, User 和 Order ,并且一个用户可以有多个订单。
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class TUsers(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(32))
orders = relationship("TOrders", back_populates="user") # 必须关联
class TOrders(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey('users.id')) # 必须使用外键
product = Column(String(32))
quantity = Column(Integer)
user = relationship("TUsers", back_populates="orders") # 必须关联
现在,如果我们想要查询所有下过订单的用户及其订单信息,我们可以进行连接查询:
from sqlalchemy.orm import joinedload
# 加载所有用户的订单信息
users_with_orders = session.query(TUsers).options(joinedload(TUsers.orders)).all()
for user in users_with_orders:
print(f"User: {user.name}")
for order in user.orders:
print(f"Order: {order.product}, Quantity: {order.quantity}, Id: {order.id}")
更多:
https://blog.csdn.net/JKQ8525350/article/details/139568447
假设我们想要统计每个用户下的订单总数。
from sqlalchemy import func
# 按用户分组,并计算每个用户的订单数量
order_count_by_user = session.query(User.id, User.name, func.count(Order.id).label('order_count')).\
join(Order).group_by(User.id, User.name).all()
for user_id, user_name, order_count in order_count_by_user:
print(f"User ID: {user_id}, Name: {user_name}, Order Count: {order_count}")
如果我们想要找出订单数量超过平均订单数量的用户,我们可以使用子查询。
from sqlalchemy import func, select
# 计算平均订单数量作为子查询
avg_order_quantity = select([func.avg(Order.quantity).label('avg_quantity')]).select_from(Order).alias()
# 查询订单数量超过平均值的用户及其订单信息
users_above_avg = session.query(User, Order.product, Order.quantity).\
join(Order).filter(Order.quantity > avg_order_quantity.c.avg_quantity).all()
for user, product, quantity in users_above_avg:
print(f"User: {user.name}, Product: {product}, Quantity: {quantity}")
假设我们想要找到名字以 “A” 开头的用户,并且他们的订单中包含 “apple” 这个产品。
# 查询名字以“A”开头的用户,且订单中包含“apple”产品的用户信息
users_with_apple = session.query(User).join(Order).\
filter(User.name.startswith('A')).\
filter(Order.product.contains('apple')).\
distinct().all() # 使用distinct() 确保结果中的用户不重复
for user in users_with_apple:
print(f"User: {user.name}")
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
# 假设已经有了一个定义好的模型和数据库引擎
engine = create_engine('sqlite:///example.db')
Session = sessionmaker(bind=engine)
session = Session()
# 分页查询函数
def paginate_query(page, page_size):
# 计算跳过的记录数
offset = (page - 1) * page_size
# 执行分页查询
results = session.query(YourModel).order_by(YourModel.id).offset(offset).limit(page_size).all()
return results
# 使用分页查询
page = 1
page_size = 10
records = paginate_query(page, page_size)
total = session.query(YourModel).with_entities(func.count(YourModel.id)).scalar()
db_query = session.query(YourModel).slice(page_start, page_end)
# size=-1 返回所有数据
def select_user_plus(self, page: int=1, size: int=-1):
query = self.__session.query(TUsers).order_by(TUsers.create_time.asc())
# 查询总条数
total = query.count()
# 排序分页
if size > 0:
# 计算起始索引
offset = (page-1) * size
query = query.offset(offset).limit(size)
else:
pass
# 查询记录
users = query.all()
print(f"User: {total} TOTAL")
for user in users:
user_base = UserBase.from_orm(user)
print(f"User: {user_base} START")
def queryByPage(page: int, pageSize: int, conditions: dict):
""" 分页查询 """
# 计算起始索引
offset = (page - 1) * pageSize
with getSession() as session:
query = session.query(YmUser)
# 填充查询条件
if len(conditions) > 0:
query = query.filter_by(**conditions)
# 查询总条数
total = query.count()
# 排序分页
query = query.order_by(desc(YmUser.id)).offset(offset).limit(pageSize)
# 查询记录
result = query.all()
return total, result
# 调用
conditions = {
"status": 1,
}
queryByPage(1, 5, conditions)
# 生成SQL
"""
SELECT * FROM ym_user
WHERE ym_user.status = 1 ORDER BY ym_user.id DESC
LIMIT 0, 5
"""
我们定义三个数据模型类:User(用户)、Post(文章)和Comment(评论)。这些类之间通过外键和关系进行关联。
# 导入 SQLAlchemy 中所需的模块
from sqlalchemy import create_engine, Column, Integer, String, Text, ForeignKey
from sqlalchemy.orm import declarative_base, relationship
# 创建一个基类,用于定义数据模型的基本结构
Base = declarative_base()
# 定义数据模型类 User,对应数据库中的 'users' 表
class User(Base):
__tablename__ = 'users'
# 定义列:id,是整数类型,作为主键
id = Column(Integer, primary_key=True)
# 定义列:username,是字符串类型,最大长度为50,唯一且不可为空
username = Column(String(50), unique=True, nullable=False)
# 定义列:email,是字符串类型,最大长度为100,唯一且不可为空
email = Column(String(100), unique=True, nullable=False)
# 定义关系,与 Post 类的关系为一对多关系,通过 back_populates 指定反向关系属性名
posts = relationship('Post', back_populates='author')
# 定义数据模型类 Post,对应数据库中的 'posts' 表
class Post(Base):
__tablename__ = 'posts'
# 定义列:id,是整数类型,作为主键
id = Column(Integer, primary_key=True)
# 定义列:title,是字符串类型,最大长度为100,不可为空
title = Column(String(100), nullable=False)
# 定义列:content,是文本类型,不可为空
content = Column(Text, nullable=False)
# 定义列:user_id,是整数类型,外键关联到 'users' 表的 id 列
user_id = Column(Integer, ForeignKey('users.id'))
# 定义关系,与 User 类的关系为多对一关系,通过 back_populates 指定反向关系属性名
author = relationship('User', back_populates='posts')
# 定义关系,与 Comment 类的关系为一对多关系,通过 back_populates 指定反向关系属性名
comments = relationship('Comment', back_populates='post')
# 定义数据模型类 Comment,对应数据库中的 'comments' 表
class Comment(Base):
__tablename__ = 'comments'
# 定义列:id,是整数类型,作为主键
id = Column(Integer, primary_key=True)
# 定义列:text,是文本类型,不可为空
text = Column(Text, nullable=False)
# 定义列:user_id,是整数类型,外键关联到 'users' 表的 id 列
user_id = Column(Integer, ForeignKey('users.id'))
# 定义列:post_id,是整数类型,外键关联到 'posts' 表的 id 列
post_id = Column(Integer, ForeignKey('posts.id'))
# 定义关系,与 User 类的关系为多对一关系
author = relationship('User')
# 定义关系,与 Post 类的关系为多对一关系,通过 back_populates 指定反向关系属性名
post = relationship('Post', back_populates='comments')
这里我们选择了 SQLite 数据库,并使用 create_all 创建相应的表。
# 导入 SQLAlchemy 中所需的模块
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# 创建一个 SQLite 数据库引擎,连接到名为 'blog.db' 的数据库文件
engine = create_engine('sqlite:///blog.db')
# 使用 Base 对象的 metadata 属性,创建数据库中定义的所有表
Base.metadata.create_all(engine)
# 使用 sessionmaker 创建一个会话类 Session,并将其绑定到上面创建的数据库引擎
Session = sessionmaker(bind=engine)
# 创建一个实例化的会话对象 session,用于进行数据库操作
session = Session()
这段代码演示了如何使用 SQLAlchemy 对数据库进行插入和查询操作。首先,创建了一个用户、一篇文章和一条评论,然后通过查询用户的方式,打印出该用户的所有文章及评论。
# 创建一个新用户对象并设置其属性
user1 = User(username='john_doe', email='[email protected]')
# 将新用户对象添加到会话,表示要进行数据库插入操作
session.add(user1)
# 提交会话,将所有在此会话中的数据库操作提交到数据库
session.commit()
# 创建一篇新文章对象并设置其属性
post1 = Post(title='Introduction to SQLAlchemy', content='SQLAlchemy is a powerful ORM for Python.')
# 将文章的作者关联到之前创建的用户
post1.author = user1
# 将新文章对象添加到会话,表示要进行数据库插入操作
session.add(post1)
# 提交会话,将所有在此会话中的数据库操作提交到数据库
session.commit()
# 创建一条新评论对象并设置其属性
comment1 = Comment(text='Great article!', author=user1, post=post1)
# 将评论对象添加到会话,表示要进行数据库插入操作
session.add(comment1)
# 提交会话,将所有在此会话中的数据库操作提交到数据库
session.commit()
# 查询用户名为 'john_doe' 的用户,并打印其所有文章及评论
user = session.query(User).filter_by(username='john_doe').first()
print(f"User: {user.username}")
# 遍历用户的所有文章
for post in user.posts:
print(f"Post: {post.title}")
# 遍历文章的所有评论
for comment in post.comments:
print(f"Comment: {comment.text}")
from sqlalchemy import create_engine
from sqlalchemy_utils import create_database, database_exists
from database import Base
from schemas import UserBase, OrderBase
def is_db_exist(db_url: str):
engine = create_engine(
db_url, max_overflow=0, pool_size=16, pool_timeout=5, pool_recycle=-1
)
if not database_exists(engine.url):
create_database(engine.url)
return False
else:
return True
def init_database(db_url: str):
# 设置连接池的大小
engine = create_engine(
db_url,
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=16, # 连接池大小
pool_timeout=5, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# 创建数据库
Base.metadata.create_all(engine)
if __name__ == '__main__':
# 数据库参考
db_host = "127.0.0.1"
db_user = "root"
db_password = "lianap"
db_url = '%s%s:%s@%s/%s' % ("mysql+pymysql://", db_user, db_password, db_host, "local_db")
if not is_db_exist(db_url):
init_database(db_url)
from alchemy import AlchemyTool
alchemytool = AlchemyTool(db_url=db_url)
db_user = alchemytool.create_user("test_user")
alchemytool.create_order(db_user.id)
alchemytool.select()
from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
# sqlalchemy
Base = declarative_base()
class TUsers(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(32))
orders = relationship("TOrders", back_populates="user") # 必须使用
class TOrders(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey('users.id')) # 必须使用外键
product = Column(String(32))
quantity = Column(Integer)
user = relationship("TUsers", back_populates="orders") # 必须使用
from pydantic import BaseModel, Field
from typing import Union, Optional, Literal, List, Dict
## 任务基础结构体
class OrderBase(BaseModel):
id: int
user_id: int
product: str
quantity: int
class Config:
from_attributes=True
class UserBase(BaseModel):
id: int
name: str
orders: List[OrderBase]
class Config:
from_attributes=True
from sqlalchemy import create_engine, and_, or_, func
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import sessionmaker, joinedload
from sqlalchemy.pool import SingletonThreadPool
from database import Base
from database import TUsers, TOrders
from schemas import UserBase, OrderBase
class AlchemyTool(object):
def __init__(self, db_url: str):
print("AlchemyTool init")
# 设置连接池的大小
db_config = {
"pool_size": 10
}
#engine = create_engine(SQLALCHEMY_DATABASE_URL, **db_config)
# engine = create_engine(db_url,
# #poolclass = SingletonThreadPool,
# connect_args = {'check_same_thread': False}
# )
engine = create_engine(
db_url, max_overflow=0, pool_size=16, pool_timeout=5, pool_recycle=-1
)
# # 创建数据库
# Base.metadata.create_all(engine)
# 创建数据库连接
self.__session = sessionmaker(autocommit=False, autoflush=False, bind=engine)()
# ############################### User ###############################
def create_user(self, name:str):
db_location = None
try:
db_location = TUsers(name=name)
self.__session.add(db_location)
self.__session.commit()
except SQLAlchemyError as e:
print(f"Error.AlchemyTool.create_user SQLAlchemyError:{str(e)}")
self.__session.rollback()
finally:
pass
return db_location
def create_order(self, user_id: int=1):
db_location = None
try:
# self.__session.begin()
for index in range(5):
db_location = TOrders(user_id=user_id, product=f"product{index}", quantity=index)
self.__session.add(db_location)
self.__session.commit()
except SQLAlchemyError as e:
print(f"Error.AlchemyTool.create_order SQLAlchemyError:{str(e)}")
self.__session.rollback()
finally:
pass
return db_location
def select(self):
users_with_orders = self.__session.query(TUsers).options(joinedload(TUsers.orders)).all()
for user in users_with_orders:
user_base = UserBase.from_orm(user) # 一次性转换成 UserBase(包含List[OrderBase])
print(f"UserBase: {user_base}")
for order in user.orders:
# print(f"Order: {order.product}, Quantity: {order.quantity}, Id: {order.id}")
pass
> python.exe .\main.py
AlchemyTool init
UserBase: id=1 name='test_user' orders=[OrderBase(id=1, user_id=1, product='product0', quantity=0), OrderBase(id=2, user_id=1, product='product1', quantity=1), OrderBase(id=3, user_id=1, product='product2', quantity=2), OrderBase(id=4, user_id=1, product='product3', quantity=3), OrderBase(id=5, user_id=1, product='product4', quantity=4)]