Python使用SqlAlchemy操作数据的常见方式详解

作者:

文章目录
  • SQLAlchemy 是一个功能强大且灵活的 Python SQL 工具包和对象关系映射(ORM)库。它被广泛用于在 Python 项目中处理关系型数据库的场景,既提供了高级的 ORM 功能,又保留了对底层 SQL 语句的强大控制力。SQLAlchemy 允许开发者通过 Python 代码与数据库进行交互,而无需直接编写 SQL 语句,同时也支持直接使用原生 SQL 进行复杂查询。下面是SQLAlchemy和我们常规数据库对象的对应关系说明。 Engine 连接对象  驱动引擎 Session 连接池 事务  由此开始查询 Model 表 类定义 Column 列   Query 若干行 可以链式添加多个条件 在使用SQLAlchemy时,通常会将其与数据库对象对应起来。 SQLAlchemy: 使用 Table 对象或 Declarative Base 中的类来表示。 对应关系: 数据库中的每一个表对应于SQLAlchemy中的一个类,该类继承自 declarative_base()。 from sqlalchemy import Column, Integer, String, create_engine from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = ‘users’ # 数据库表名 id = Column(Integer, primary_key=True) name = Column(String) email = Column(String) 数据库列 (Database Column): 使用 Column 对象来表示。每个数据库表中的列在SQLAlchemy中表示为 Column 对象,并作为类的属性定义。 id = Column(Integer, primary_key=True) name = Column(String(50)) 数据库行 (Database Row): 每个数据库表的一个实例(对象)代表数据库表中的一行。在SQLAlchemy中,通过实例化模型类来表示数据库表中的一行。 new_user = User(id=1, name=’John Doe’, email=’john@example.com’) 主键 (Primary Key) :使用 primary_key=True 参数定义主键。 id = Column(Integer, primary_key=True) 外键 (Foreign Key): 使用 ForeignKey 对象来表示。 from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship class Address(Base): __tablename__ = ‘addresses’ id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey(‘users.id’)) user = relationship(‘User’) 关系 (Relationships): 使用 relationship 对象来表示。数据库中表与表之间的关系在SQLAlchemy中通过 relationship 来定义。 addresses = relationship(“Address”, back_populates=”user”)
  • 下面我们通过异步处理的方式,介绍如何在单表中操作相关的数据库数据。 async def get(self, db: AsyncSession, id: Any) -> Any: “””根据主键获取一个对象””” if isinstance(id, str): query = select(self.model).filter(func.lower(self.model.id) == id.lower()) else: query = select(self.model).filter(self.model.id == id) result = await db.execute(query) item = result.scalars().first() return item 如果我们需要强制对外键的类型进行匹配(如对于Postgresql的严格要求,数据比较的类型必须一致),那么我们需要在基类或者CRUD类初始化的时候,获得对应的主键类型。 class BaseCrud(Generic[ModelType, PrimaryKeyType, PageDtoType, DtoType]): “”” 基础CRUD操作类,传入参数说明: * `ModelType`: SQLAlchemy 模型类 * `PrimaryKeyType`: 限定主键的类型 * `PageDtoType`: 分页查询输入类 * `DtoType`: 数据传输对象类,如新增、更新的单个对象DTO “”” def __init__(self, model: Type[ModelType]): “”” 数据库访问操作的基类对象(CRUD). * `model`: A SQLAlchemy model class “”” self.model = model # 模型类型 # 运行期获取主键字段类型 pk_column = inspect(model).primary_key[0] self._pk_type = pk_column.type.python_type # int / str 因此对于单表的Get方法,我们修改下,让他匹配主键的类型进行比较,这样过对于严格类型判断的Postgresql也正常匹配了。 async def get(self, db: AsyncSession, id: PrimaryKeyType) -> Optional[ModelType]: “””根据主键获取一个对象””” #对id的主键进行类型转换,self._pk_type在构造函数的初始化中获取 try: id = self._pk_type(id) except Exception: raise ValueError(f”Invalid primary key type: {id}”) if isinstance(id, str): query = select(self.model).filter(func.lower(self.model.id) == id.lower()) else: query = select(self.model).filter(self.model.id == id) result = await db.execute(query) item = result.scalars().first() return item 对于删除的数据,我们也可以类似的处理对比进行了。 from sqlalchemy.orm import Session, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import delete as sa_delete, update as sa_update async def delete_byid(self, db: AsyncSession, id: PrimaryKeyType) -> bool: “””根据主键删除一个对象 :param id: 主键值 “”” #对id的主键进行类型转换,self._pk_type在构造函数的初始化中获取 try: id = self._pk_type(id) except Exception: raise ValueError(f”Invalid primary key type: {id}”) del_query: sa_delete if isinstance(id, str): del_query = sa_delete(self.model).where( func.lower(self.model.id) == id.lower() ) else: del_query = sa_delete(self.model).where(self.model.id == id) result = await db.execute(del_query) await db.commit() return result.rowcount > 0 对于提供多条件的查询或者过滤,我们可以使用where函数或者filter函数,在 SQLAlchemy 中,select(…).where(…) 和 select(…).filter(…) 都用于构造查询条件,如下所示等效。 query = select(self.model).where(self.model.id == id) query = select(self.model).filter(self.model.id == id) 我们可以通过sqlAlchemy的and_和or_函数来进行组合多个条件。 from sqlalchemy import ( Table,Column,and_,or_,asc,desc,select,func,distinct,text, Integer) …. match expression: case “and”: query = await db.execute( select(self.model) .filter(and_( * where_list)) .order_by(*order_by_list) ) case “or”: query = await db.execute( select(self.model).filter(or_( *where_list)) .order_by(*order_by_list) ) Python的SqlAlchemy提供 InstrumentedAttribute 对象来操作多个条件,如我们对于一些多条件的处理,可以利用它来传递多个参数。 async def get_all_by_attributes( self, db: AsyncSession, *attributes: InstrumentedAttribute, sorting: str = “” ) -> List[ModelType] | None: “””根据列名称和值获取相关的对象列表 :param sorting: 格式:name asc 或 name asc,age desc :param attributes: SQLAlchemy InstrumentedAttribute objects,可以输入多个条件 例子:User.id != 1 或者 User.username == “JohnDoe” “”” order_by_list = parse_sort_string(sorting, self.model) query = select(self.model).filter(and_(*attributes)).order_by(*order_by_list) result = await db.execute(query) return result.scalars().all() 例如,对于 模型 Material 对象,我们对它进行多个条件的查询处理,如下所示,红色部分为 *attributes: InstrumentedAttribute 参数。 items = await super().get_all_by_attributes( db, Material.id == vercol.id, Material.vercol == vercol.vercol, Material.ischecked == 0, Material.status == 0, ) 同样我们可以利用它来获取数量,或者判断多条件的记录是否存在。  在数据插入或者更新的操作中,我们可以接受对象类型或者字典类型的参数对象,因此方法如下所示。 async def update(self, db: AsyncSession, obj_in: DtoType | dict[str, Any]) -> bool: “””更新对象 :param obj_in: 对象输入数据,可以是 DTO 对象或字典 “”” try: if isinstance(obj_in, dict): obj_id = obj_in.get(“id”) if obj_id is None: raise ValueError(“id is required for update”) update_data = obj_in else: obj_id = obj_in.id # update_data = vars(obj_in) update_data = obj_in.model_dump(exclude_unset=True) query = select(self.model).filter(self.model.id == obj_id) result = await db.execute(query) db_obj = result.scalars().first() if db_obj: # 更新对象字段 for field, value in update_data.items(): # 跳过以 “_” 开头的私有属性 if field.startswith(“_”): continue setattr(db_obj, field, value) # 处理更新前的回调处理 self.on_before_update(update_data, db_obj) # 提交事务 await db.commit() return True else: return False except SQLAlchemyError as e: self.logger.error(f”update 操作出现错误: {e}”) await db.rollback() # 确保在出错时回滚事务 return False 我们在插入或者更新数据的时候,一般会默认更新一些字段,如创建人,创建日期、编辑人,编辑日期等信息,我们可以把它单独作为一个可以给子类重写的函数,基类做一些默认的处理。 def on_before_update(self, update_data: dict[str, Any], db_obj: ModelType) -> None: “””更新对象前的回调函数,子类可以重写此方法 可通过 setattr(db_obj, field, value) 设置字段值 “”” setattr(db_obj, “edittime”, datetime.now()) user :CurrentUserIns = get_current_user() if user: setattr(db_obj, “editor”, user.fullname) setattr(db_obj, “editor_id”, user.id) setattr(db_obj, “company_id”, user.company_id) setattr(db_obj, “companyname”, user.companyname) 有时候,如果我们需要获取某个字段非重复的列表,用来做为动态下拉列表的数据,那么我们可以通过下面函数封装下。 async def get_field_list(self, db: AsyncSession, field_name: str) -> Iterable[str]: “””获取指定字段值的唯一列表 :param field_name: 字段名称 “”” field = getattr(self.model, field_name) query = select(distinct(field)) result = await db.execute(query) return result.scalars().all()
  • 多表操作,也是我们经常碰到的处理方式,如对于字典类型和字典项目,他们是两个表,需要联合起来获取数据,那么就需要多表的联合操作。  如下是字典CRUD类中,联合字典类型获取数据的记录处理。 async def get_dict_by_typename(self, db: AsyncSession, dicttype_name: str) -> dict: “””根据字典类型名称获取所有该类型的字典列表集合””” result = await db.execute( select(self.model) .join(DictType, DictType.id == self.model.dicttype_id) # 关联字典类型表 .filter(DictType.name == dicttype_name) # 过滤字典类型名称 .order_by(DictData.seq) # 排序 ) items = result.scalars().all() dict = {} for info in items: if info.name not in dict: dict[info.name] = info.value return dict 如果我们需要对某个表的递归获取树列表,可以如下处理 async def get_tree(self, db: AsyncSession, pid: str) -> list[DictType]: “””获取字典类型一级列表及其下面的内容””” # 使用三元运算符将 pid 设为 “-1″(如果 pid 是 null 或空白)或保持原值 pid = “-1” if not pid or pid.strip() == “” else pid result = await db.execute( select(self.model) .filter(self.model.pid == pid) .options(selectinload(DictType.children)) ) nodes = result.scalars().all() return nodes 我们来假设用户和文章的示例表结构(ORM 模型,如下所示。 class User(Base): __tablename__ = “users” id = Column(Integer, primary_key=True, index=True) name = Column(String) email = Column(String) articles = relationship(“Article”, back_populates=”author”) class Article(Base): __tablename__ = “articles” id = Column(Integer, primary_key=True, index=True) title = Column(String) content = Column(Text) user_id = Column(Integer, ForeignKey(“users.id”)) author = relationship(“User”, back_populates=”articles”) 我们可以通过下面函数处理获得相关的记录集合。 async def get_user_articles(db: AsyncSession): stmt = ( select(User, Article) .join(Article, Article.user_id == User.id) ) result = await db.execute(stmt) return result.all() # [ (User(), Article()), . ..] 如果需要可以使用outer_join函数处理 async def get_users_with_articles(db: AsyncSession): stmt = ( select(User, Article) .outerjoin(Article, Article.user_id == User.id) ) result = await db.execute(stmt) return result.all() # 用户即便没有文章也会出现 如果我们需要获取有文章的所有用户,如下所示。 async def get_users_with_articles(db: AsyncSession): stmt = select(User).options(selectinload(User.articles)) # 自动 load 关联 result = await db.execute(stmt) return result.scalars().all() selectinload 会执行两次 SQL,但效率高,不会产生笛卡尔积,非常适合集合查询。 多表链式 Join的处理,可以获得两个表的不同信息进行组合。 async def get_articles_with_author(db: AsyncSession): stmt = ( select(Article.title, User.name.label(“author”)) .join(User, Article.user_id == User.id) ) rows = await db.execute(stmt) return rows.mappings().all() # 以 dict 形式返回 [{‘title’:…, ‘author’:…}] 带筛选条件与分页的处理实现,如下所示 async def search_articles(db: AsyncSession, keyword: str, page: int = 1, size: int = 10): stmt = ( select(Article, User.name.label(“author”)) .join(User) .filter(Article.title.contains(keyword)) .offset((page – 1) * size) .limit(size) ) result = await db.execute(stmt) return result.mappings().all()   对于权限管理系统来说,一般有用户、角色,以及用户角色的中间表,我们来看看这个在SQLAlchemy最佳实践是如何的操作。 from sqlalchemy import Table, Column, Integer, ForeignKey from sqlalchemy.orm import relationship, Mapped, mapped_column from database import Base # — 中间表写法 — role_user = Table( “role_user”, Base.metadata, Column(“user_id”, ForeignKey(“users.id”), primary_key=True), Column(“role_id”, ForeignKey(“roles.id”), primary_key=True) ) class User(Base): __tablename__ = “users” id: Mapped[int] = mapped_column(primary_key=True) username: Mapped[str] = mapped_column() roles: Mapped ] = relationship( secondary=role_user, back_populates=”users”, lazy=”selectin” ) class Role(Base): __tablename__ = “roles” id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column() users: Mapped ] = relationship( secondary=role_user, back_populates=”roles”, lazy=”selectin” ) 在 SQLAlchemy 声明多对多关系时,secondary 参数既可以填 字符串形式的表名,也可以填 已经定义好的中间表对象(Table 对象)。 ① econdary="role_user" —— 使用字符串表名 roles = relationship(“Role”, secondary=”role_user”, back_populates=”users”) ② secondary=role_user —— 传入中间表对象(推荐方式) role_user = Table( “role_user”, Base.metadata, Column(“user_id”, ForeignKey(“users.id”), primary_key=True), Column(“role_id”, ForeignKey(“roles.id”), primary_key=True) ) roles = relationship(“Role”, secondary=role_user, back_populates=”users”) 对于如果获取对应角色的用户记录,我们可以通过下面方式获取(通过连接中间表的方式) async def get_users_by_role(db: AsyncSession, role_id: int) -> list[User]: stmt = ( select(User) .join(role_user, role_user.c.user_id == User.id) .where(role_user.c.role_id == role_id) ) result = await db.execute(stmt) return result.scalars().all() 也可以下面的方式进行处理(使用 relationship any()),效果是一样的。 select(User).filter(User.roles.any(id=role_id)) 如果需要写入用户、角色的关联关系,我们可以使用下面方法来通过中间表进行判断并写入记录。 from sqlalchemy import select, insert async def add_users_to_role(db: AsyncSession, role_id: int, user_ids: list[int]): # 1️⃣ 查询已有关联 user_id stmt = select(role_user.c.user_id).where(role_user.c.role_id == role_id) res = await db.execute(stmt) existing_user_ids = {row[0] for row in res.fetchall()} # 2️⃣ 过滤出新的 user_id new_user_ids = [uid for uid in user_ids if uid not in existing_user_ids] if not new_user_ids: return 0 # 没有新增 # 3️⃣ 批量插入 values = [{“user_id”: uid, “role_id”: role_id} for uid in new_user_ids] stmt = insert(role_user).values(values) await db.execute(stmt) await db.commit() return len(new_user_ids) 如果只是单个记录的插入,可以利用下面的方式处理。 from sqlalchemy import select, insert async def add_user_to_role(db: AsyncSession, role_id: int, user_id: int) -> bool: # 1️⃣ 检查是否已存在关联 stmt = select(role_user).where( role_user.c.role_id == role_id, role_user.c.user_id == user_id ) res = await db.execute(stmt) exists = res.first() if exists: return False # 已存在,不再插入 # 2️⃣ 插入记录 stmt = insert(role_user).values(user_id=user_id, role_id=role_id) await db.execute(stmt) await db.commit() return True 以上就是对于在Python+FastAPI的后端项目中使用SqlAlchemy操作数据的几种常见方式,包括单表处理,多表关联、中间表的数据维护和定义等内容,是我们在操作常规数据的时候,经常碰到的几种方式。 到此这篇关于Python使用SqlAlchemy操作数据的常见方式详解的文章就介绍到这了,更多相关Python SqlAlchemy操作数据内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Python使用SQLAlchemy操作单表的完整实战指南 Python库SQLAlchemy的简介、用法和安装步骤详解 使用Python和SQLAlchemy实现高效的邮件发送系统 Python使用SQLAlchemy连接ClickHouse数据库 python SQLAlchemy 数据库连接池的实现 Python中SQLAlchemy库的使用方法分析 Python使用SQLAlchemy操作Mysql数据库的操作示例
  • 目录
    • 1、SQLAlchemy介绍
    • 2、常规的单表处理
    • 3、多表联合的处理操作

    在Python+FastAPI的后端项目中,我们往往很多时候需要对数据进行相关的处理,本篇随笔介绍在Python+FastAPI项目中使用SqlAlchemy操作数据的几种常见方式。

    使用 FastAPI, SQLAlchemy, Pydantic构建后端项目的时候,其中数据库访问采用SQLAlchemy 的异步方式处理。一般我们在操作数据库操作的时候,采用基类继承的方式减少重复代码,提高代码复用性。不过我们在分析SQLAlchemy的时候,我们可以简单的方式来剖析几种常见的数据库操作方式,来介绍SQLAlchemy的具体使用。

    SQLAlchemy 是一个功能强大且灵活的 Python SQL 工具包和对象关系映射(ORM)库。它被广泛用于在 Python 项目中处理关系型数据库的场景,既提供了高级的 ORM 功能,又保留了对底层 SQL 语句的强大控制力。SQLAlchemy 允许开发者通过 Python 代码与数据库进行交互,而无需直接编写 SQL 语句,同时也支持直接使用原生 SQL 进行复杂查询。下面是SQLAlchemy和我们常规数据库对象的对应关系说明。

    Engine 连接对象  驱动引擎
    Session 连接池 事务  由此开始查询
    Model 类定义
    Column 列  
    Query 若干行 可以链式添加多个条件

    在使用SQLAlchemy时,通常会将其与数据库对象对应起来。

    SQLAlchemy: 使用 Table 对象或 Declarative Base 中的类来表示。

    对应关系: 数据库中的每一个表对应于SQLAlchemy中的一个类,该类继承自 declarative_base()

    from sqlalchemy import Column, Integer, String, create_engine
    from sqlalchemy.ext.declarative import declarative_base
    
    Base = declarative_base()
    
    class User(Base):
        __tablename__ = 'users'  # 数据库表名
        id = Column(Integer, primary_key=True)
        name = Column(String)
        email = Column(String)
    

    数据库列 (Database Column): 使用 Column 对象来表示。每个数据库表中的列在SQLAlchemy中表示为 Column 对象,并作为类的属性定义。

    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    

    数据库行 (Database Row): 每个数据库表的一个实例(对象)代表数据库表中的一行。在SQLAlchemy中,通过实例化模型类来表示数据库表中的一行。

    new_user = User(id=1, name='John Doe', email='john@example.com')
    

    主键 (Primary Key) :使用 primary_key=True 参数定义主键。

    id = Column(Integer, primary_key=True)
    

    外键 (Foreign Key): 使用 ForeignKey 对象来表示。

    from sqlalchemy import ForeignKey
    from sqlalchemy.orm import relationship
    
    class Address(Base):
        __tablename__ = 'addresses'
        id = Column(Integer, primary_key=True)
        user_id = Column(Integer, ForeignKey('users.id'))
        user = relationship('User')
    

    关系 (Relationships): 使用 relationship 对象来表示。数据库中表与表之间的关系在SQLAlchemy中通过 relationship 来定义。

    addresses = relationship("Address", back_populates="user")
    

    下面我们通过异步处理的方式,介绍如何在单表中操作相关的数据库数据。

    async def get(self, db: AsyncSession, id: Any) -> Any:
        """根据主键获取一个对象"""
    
        if isinstance(id, str):
            query = select(self.model).filter(func.lower(self.model.id) == id.lower())
        else:
            query = select(self.model).filter(self.model.id == id)
    
        result = await db.execute(query)
        item = result.scalars().first()
    
        return item
    

    如果我们需要强制对外键的类型进行匹配(如对于Postgresql的严格要求,数据比较的类型必须一致),那么我们需要在基类或者CRUD类初始化的时候,获得对应的主键类型。

    class BaseCrud(Generic[ModelType, PrimaryKeyType, PageDtoType, DtoType]):
        """
        基础CRUD操作类,传入参数说明:
        * `ModelType`: SQLAlchemy 模型类
        * `PrimaryKeyType`: 限定主键的类型
        * `PageDtoType`: 分页查询输入类
        * `DtoType`: 数据传输对象类,如新增、更新的单个对象DTO
        """
    
        def __init__(self, model: Type[ModelType]):
            """
            数据库访问操作的基类对象(CRUD).
            * `model`: A SQLAlchemy model class
            """
            
            self.model = model  # 模型类型
    
            # 运行期获取主键字段类型
            pk_column = inspect(model).primary_key[0]
            self._pk_type = pk_column.type.python_type  # int / str
    

    因此对于单表的Get方法,我们修改下,让他匹配主键的类型进行比较,这样过对于严格类型判断的Postgresql也正常匹配了。

        async def get(self, db: AsyncSession, id: PrimaryKeyType) -> Optional[ModelType]:
            """根据主键获取一个对象"""
            
            #对id的主键进行类型转换,self._pk_type在构造函数的初始化中获取
            try:
                id = self._pk_type(id)
            except Exception:
                raise ValueError(f"Invalid primary key type: {id}")
            
            if isinstance(id, str):
                query = select(self.model).filter(func.lower(self.model.id) == id.lower())
            else:
                query = select(self.model).filter(self.model.id == id)
    
            result = await db.execute(query)
            item = result.scalars().first()
    
            return item
    

    对于删除的数据,我们也可以类似的处理对比进行了。

    from sqlalchemy.orm import Session, Query
    from sqlalchemy.ext.asyncio import AsyncSession
    from sqlalchemy import delete as sa_delete, update as sa_update
    
    async def delete_byid(self, db: AsyncSession, id: PrimaryKeyType) -> bool:
        """根据主键删除一个对象
        
        :param id: 主键值
        """
        
        #对id的主键进行类型转换,self._pk_type在构造函数的初始化中获取
        try:
            id = self._pk_type(id)
        except Exception:
            raise ValueError(f"Invalid primary key type: {id}")
        
        del_query: sa_delete
        if isinstance(id, str):
            del_query = sa_delete(self.model).where(
                func.lower(self.model.id) == id.lower()
            )
        else:
            del_query = sa_delete(self.model).where(self.model.id == id)
    
        result = await db.execute(del_query)
    
        await db.commit()
        return result.rowcount > 0
    

    对于提供多条件的查询或者过滤,我们可以使用where函数或者filter函数,在 SQLAlchemy 中,select(...).where(...) 和 select(...).filter(...) 都用于构造查询条件,如下所示等效。

    query = select(self.model).where(self.model.id == id)
    
    query = select(self.model).filter(self.model.id == id)
    

    我们可以通过sqlAlchemy的and_和or_函数来进行组合多个条件。

    from sqlalchemy import ( Table,Column,and_,or_,asc,desc,select,func,distinct,text, Integer)
    
    ....
        match expression:
            case "and":
                query = await db.execute(
                    select(self.model)
                    .filter(and_(  * where_list)) 
                    .order_by(*order_by_list)
                )
            case "or":
                query = await db.execute(
                    select(self.model).filter(or_(  *where_list)) .order_by(*order_by_list)
                )
    

    Python的SqlAlchemy提供 InstrumentedAttribute 对象来操作多个条件,如我们对于一些多条件的处理,可以利用它来传递多个参数。

        async def get_all_by_attributes(
            self, db: AsyncSession, *attributes: InstrumentedAttribute, sorting: str = ""
        ) -> List[ModelType] | None:
            """根据列名称和值获取相关的对象列表
    
            :param sorting: 格式:name asc 或 name asc,age desc
            :param attributes: SQLAlchemy InstrumentedAttribute objects,可以输入多个条件
            例子:User.id != 1 或者 User.username == "JohnDoe"
            """
    
            order_by_list = parse_sort_string(sorting, self.model)
            query = select(self.model).filter(and_(*attributes)).order_by(*order_by_list)
            
            result = await db.execute(query)
            return result.scalars().all()
    

    例如,对于 模型 Material 对象,我们对它进行多个条件的查询处理,如下所示,红色部分为 *attributes: InstrumentedAttribute 参数。

    items = await super().get_all_by_attributes(
        db,
     Material.id == vercol.id,
        Material.vercol == vercol.vercol,
        Material.ischecked == 0,
        Material.status == 0, 
    )
    

    同样我们可以利用它来获取数量,或者判断多条件的记录是否存在。

     在数据插入或者更新的操作中,我们可以接受对象类型或者字典类型的参数对象,因此方法如下所示。

       async def update(self, db: AsyncSession, obj_in: DtoType | dict[str, Any]) -> bool:
            """更新对象
            
            :param obj_in: 对象输入数据,可以是 DTO 对象或字典
            """
            try:
                if isinstance(obj_in, dict):
                    obj_id = obj_in.get("id")
                    if obj_id is None:
                        raise ValueError("id is required for update")
                    update_data = obj_in
                else:
                    obj_id = obj_in.id
                    # update_data = vars(obj_in)  
                    update_data = obj_in.model_dump(exclude_unset=True)
    
                query = select(self.model).filter(self.model.id == obj_id)
                result = await db.execute(query)
                db_obj = result.scalars().first()
    
                if db_obj:
                    # 更新对象字段
                    for field, value in update_data.items():
                        # 跳过以 "_" 开头的私有属性
                        if field.startswith("_"):
                            continue
                        setattr(db_obj, field, value)
    
                    # 处理更新前的回调处理
     self.on_before_update(update_data, db_obj) # 提交事务
                    await db.commit()
                    return True
                else:
                    return False
            except SQLAlchemyError as e:
                self.logger.error(f"update 操作出现错误: {e}")
                await db.rollback()  # 确保在出错时回滚事务
                return False
    

    我们在插入或者更新数据的时候,一般会默认更新一些字段,如创建人,创建日期、编辑人,编辑日期等信息,我们可以把它单独作为一个可以给子类重写的函数,基类做一些默认的处理。

        def on_before_update(self, update_data: dict[str, Any], db_obj: ModelType) -> None:
            """更新对象前的回调函数,子类可以重写此方法
    
            可通过 setattr(db_obj, field, value) 设置字段值
            """
            
            setattr(db_obj, "edittime", datetime.now())
            user :CurrentUserIns  = get_current_user()
            if user:
                setattr(db_obj, "editor", user.fullname)
                setattr(db_obj, "editor_id", user.id)
    
                setattr(db_obj, "company_id", user.company_id)
                setattr(db_obj, "companyname", user.companyname)
    

    有时候,如果我们需要获取某个字段非重复的列表,用来做为动态下拉列表的数据,那么我们可以通过下面函数封装下。

        async def get_field_list(self, db: AsyncSession, field_name: str) -> Iterable[str]:
            """获取指定字段值的唯一列表
    
            :param field_name: 字段名称
            """
    
            field = getattr(self.model, field_name)
            query = select(distinct(field))
            result = await db.execute(query)
            return result.scalars().all()
    

    多表操作,也是我们经常碰到的处理方式,如对于字典类型和字典项目,他们是两个表,需要联合起来获取数据,那么就需要多表的联合操作。

     如下是字典CRUD类中,联合字典类型获取数据的记录处理。

        async def get_dict_by_typename(self, db: AsyncSession, dicttype_name: str) -> dict:
            """根据字典类型名称获取所有该类型的字典列表集合"""
            result = await db.execute(
                select(self.model)
                 .join(DictType, DictType.id == self.model.dicttype_id)   # 关联字典类型表
                .filter(DictType.name == dicttype_name) # 过滤字典类型名称
                .order_by(DictData.seq)  # 排序
            )
            items = result.scalars().all()
    
            dict = {}
            for info in items:
                if info.name not in dict:
                    dict[info.name] = info.value
    
            return dict
    

    如果我们需要对某个表的递归获取树列表,可以如下处理

        async def get_tree(self, db: AsyncSession, pid: str) -> list[DictType]:
            """获取字典类型一级列表及其下面的内容"""
    
            # 使用三元运算符将 pid 设为 "-1"(如果 pid 是 null 或空白)或保持原值
            pid = "-1" if not pid or pid.strip() == "" else pid
            result = await db.execute(
                select(self.model)
                .filter(self.model.pid == pid)
                .options(selectinload(DictType.children))
            )
            nodes = result.scalars().all()
            return nodes
    

    我们来假设用户和文章的示例表结构(ORM 模型,如下所示。

    class User(Base):
        __tablename__ = "users"
        id = Column(Integer, primary_key=True, index=True)
        name = Column(String)
        email = Column(String)
        articles = relationship("Article", back_populates="author")
    
    
    class Article(Base):
        __tablename__ = "articles"
        id = Column(Integer, primary_key=True, index=True)
        title = Column(String)
        content = Column(Text)
        user_id = Column(Integer, ForeignKey("users.id"))
        author = relationship("User", back_populates="articles")
    

    我们可以通过下面函数处理获得相关的记录集合。

    async def get_user_articles(db: AsyncSession):
        stmt = (
            select(User, Article)
            .join(Article, Article.user_id == User.id)
        )
        result = await db.execute(stmt)
        return result.all()   # [ (User(), Article()), . ..]
    

    如果需要可以使用outer_join函数处理

    async def get_users_with_articles(db: AsyncSession):
        stmt = (
            select(User, Article)
            .outerjoin(Article, Article.user_id == User.id)
        )
        result = await db.execute(stmt)
        return result.all()  # 用户即便没有文章也会出现
    

    如果我们需要获取有文章的所有用户,如下所示。

    async def get_users_with_articles(db: AsyncSession):
        stmt = select(User).options(selectinload(User.articles))  # 自动 load 关联
        result = await db.execute(stmt)
        return result.scalars().all()
    

    selectinload 会执行两次 SQL,但效率高,不会产生笛卡尔积,非常适合集合查询。

    多表链式 Join的处理,可以获得两个表的不同信息进行组合。

    async def get_articles_with_author(db: AsyncSession):
        stmt = (
            select(Article.title, User.name.label("author"))
            .join(User, Article.user_id == User.id)
        )
        rows = await db.execute(stmt)
        return rows.mappings().all()   # 以 dict 形式返回 [{'title':..., 'author':...}]
    

    带筛选条件与分页的处理实现,如下所示

    async def search_articles(db: AsyncSession, keyword: str, page: int = 1, size: int = 10):
        stmt = (
            select(Article, User.name.label("author"))
            .join(User)
            .filter(Article.title.contains(keyword))
            .offset((page - 1) * size)
            .limit(size)
        )
        result = await db.execute(stmt)
        return result.mappings().all()
    

      对于权限管理系统来说,一般有用户、角色,以及用户角色的中间表,我们来看看这个在SQLAlchemy最佳实践是如何的操作。

    from sqlalchemy import Table, Column, Integer, ForeignKey
    from sqlalchemy.orm import relationship, Mapped, mapped_column
    from database import Base
    
    # --- 中间表写法 ---
    role_user = Table(
        "role_user",
        Base.metadata,
        Column("user_id", ForeignKey("users.id"), primary_key=True),
        Column("role_id", ForeignKey("roles.id"), primary_key=True)
    )
    
    class User(Base):
        __tablename__ = "users"
        id: Mapped[int] = mapped_column(primary_key=True)
        username: Mapped[str] = mapped_column()
    
        roles: Mapped
    ] = relationship( secondary=role_user, back_populates="users", lazy="selectin" ) class Role(Base): __tablename__ = "roles" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column() users: Mapped
    ] = relationship( secondary=role_user, back_populates="roles", lazy="selectin" )

    在 SQLAlchemy 声明多对多关系时,secondary 参数既可以填 字符串形式的表名,也可以填 已经定义好的中间表对象(Table 对象)。

    ① econdary="role_user" —— 使用字符串表名

    roles = relationship("Role", secondary="role_user", back_populates="users")
    

    ② secondary=role_user —— 传入中间表对象(推荐方式)

    role_user = Table(
        "role_user",
        Base.metadata,
        Column("user_id", ForeignKey("users.id"), primary_key=True),
        Column("role_id", ForeignKey("roles.id"), primary_key=True)
    )
    
    roles = relationship("Role", secondary=role_user, back_populates="users")
    

    对于如果获取对应角色的用户记录,我们可以通过下面方式获取(通过连接中间表的方式)

    async def get_users_by_role(db: AsyncSession, role_id: int) -> list[User]:
        stmt = (
            select(User)
            .join(role_user, role_user.c.user_id == User.id)
            .where(role_user.c.role_id == role_id)
        )
        result = await db.execute(stmt)
        return result.scalars().all()
    

    也可以下面的方式进行处理(使用 relationship any()),效果是一样的。

    select(User).filter(User.roles.any(id=role_id))
    

    如果需要写入用户、角色的关联关系,我们可以使用下面方法来通过中间表进行判断并写入记录。

    from sqlalchemy import select, insert
    
    async def add_users_to_role(db: AsyncSession, role_id: int, user_ids: list[int]):
        # 1️⃣ 查询已有关联 user_id
        stmt = select(role_user.c.user_id).where(role_user.c.role_id == role_id)
        res = await db.execute(stmt)
        existing_user_ids = {row[0] for row in res.fetchall()}
    
        # 2️⃣ 过滤出新的 user_id
        new_user_ids = [uid for uid in user_ids if uid not in existing_user_ids]
        if not new_user_ids:
            return 0  # 没有新增
    
        # 3️⃣ 批量插入
        values = [{"user_id": uid, "role_id": role_id} for uid in new_user_ids]
        stmt = insert(role_user).values(values)
        await db.execute(stmt)
        await db.commit()
    
        return len(new_user_ids)
    

    如果只是单个记录的插入,可以利用下面的方式处理。

    from sqlalchemy import select, insert
    
    async def add_user_to_role(db: AsyncSession, role_id: int, user_id: int) -> bool:
        # 1️⃣ 检查是否已存在关联
        stmt = select(role_user).where(
            role_user.c.role_id == role_id,
            role_user.c.user_id == user_id
        )
        res = await db.execute(stmt)
        exists = res.first()
    
        if exists:
            return False  # 已存在,不再插入
    
        # 2️⃣ 插入记录
        stmt = insert(role_user).values(user_id=user_id, role_id=role_id)
        await db.execute(stmt)
        await db.commit()
    
        return True
    

    以上就是对于在Python+FastAPI的后端项目中使用SqlAlchemy操作数据的几种常见方式,包括单表处理,多表关联、中间表的数据维护和定义等内容,是我们在操作常规数据的时候,经常碰到的几种方式。

    到此这篇关于Python使用SqlAlchemy操作数据的常见方式详解的文章就介绍到这了,更多相关Python SqlAlchemy操作数据内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • Python使用SQLAlchemy操作单表的完整实战指南
    • Python库SQLAlchemy的简介、用法和安装步骤详解
    • 使用Python和SQLAlchemy实现高效的邮件发送系统
    • Python使用SQLAlchemy连接ClickHouse数据库
    • python SQLAlchemy 数据库连接池的实现
    • Python中SQLAlchemy库的使用方法分析
    • Python使用SQLAlchemy操作Mysql数据库的操作示例

    站内搜索