@@ -8,7 +8,7 @@ tags = ['Python', 'Database']
88
99### SQLALchemy Core and SQLALchemy ORM
1010
11- SQLALchemy 分位两个模块 :Core 和 ORM (Object-Relational Mapping)。
11+ SQLALchemy 分为两个模块 :Core 和 ORM (Object-Relational Mapping)。
1212Core 模块包含对所有受支持数据库方言的集成逻辑,一组用于描述数据库表的类,用于 Python 语言生成 SQL 语句。
1313ORM 模块在 Python 应用程序中引入了一层抽象,使得许多数据库操作可以根据对 Python 对象执行的操作自动推导出来。
1414
@@ -140,6 +140,8 @@ Model 子类通过类属性定义:
140140
141141` __tablename__ ` 属性定义数据库表名称,常见命名规范是表名称使用复数小写形式,这和模型名称形成对比,即使用单数的 camel case 驼峰命名法。
142142
143+ > ` Mapped[] ` 是一个范型容器,主要作用是告诉 IDE 和静态检查器,这个属性在 Python 层属于什么类型。
144+
143145剩余定义的属性是表中的列,` Mapped[t] ` 类型声明用于定义每个列,其中 ` t ` 是 Python 类型,例如 ` int ` , ` str ` 或 ` datetime ` 。
144146对于 year 这样的普通列来说完全足够了,如果列需要额外的功能,就要将其赋值给一个 ` mapped_column() ` 构造器。
145147
@@ -788,3 +790,211 @@ session.get(Product, 23) # Product(23, 'CT-80')
788790如果结果不存在,则会返回 ` None ` 。
789791
790792## Indexs
793+
794+ 数据库在搜索信息的时候,实际上有多种不同的算法。
795+ 当给予一个查询时,数据库会决定使用更合适更高效的算法。
796+
797+ 有一种算法总是可用的:表扫描 table scan。
798+ 表扫描 table scan 操作包括在按顺序读取条目时,对所有行依次评估 evaluate 查询过滤器 query filters。
799+
800+ 表扫描是数据库没有其他方法后,最后会使用的算法,或者当的表足够小,没必要使用复杂算法时。
801+ 作为数据库的设计者,需要确保数据通过合适的方式进行索引,从而支持更加复杂的搜索算法。
802+
803+ 当列被标记为索引后,数据库会维护一个二叉树结构的数据结构,来让该列的查询和排序更加高效。
804+
805+ 例如下面的例子:
806+
807+ - id
808+ - name
809+ - manufacturer
810+ - year
811+
812+ ` id ` 列是主键,数据库会自动将该列索引,因此根据主键搜索有很多优化。
813+ 而 ` name ` , ` manufacturer ` 和 ` year ` 这样的列经常用于 ` where() ` , ` group_by() ` 和 ` order_by() ` 之类的查询,当前没有被索引,因此会一行一行查询。
814+
815+ 对于需要索引的列,在模型定义中添加 ` index=True ` :
816+
817+ ``` Python
818+ class Product (Model ):
819+ __tablename__ = ' products'
820+
821+ id : Mapped[int ] = mapped_column(primary_key = True )
822+ name: Mapped[str ] = mapped_column(String(64 ), index = True )
823+ manufacturer: Mapped[str ] = mapped_column(String(64 ), index = True )
824+ year: Mapped[int ] = mapped_column(index = True )
825+ country: Mapped[str ] = mapped_column(String(32 ))
826+ cpu: Mapped[str ] = mapped_column(String(32 ))
827+
828+ def __repr__ (self ):
829+ return f ' Product( { self .id} , " { self .name} ") '
830+ ```
831+
832+ ## Constrains
833+
834+ 另一个好的数据库设计实践是为数据库分配约束。
835+ ` Product ` 模型有一个叫做 ` PRIMARY KEY ` 的约束,即 ` primary_key=True ` 参数。
836+ 除了主键外,还有 ` UNIQUE ` 和 ` NOT NULL ` 这些其他列常用的约束。
837+
838+ 有唯一约束 ` UNIQUE ` 的列不能有重复值,通过参数 ` unique=True ` 实现。
839+ 非空约束 ` NOT NULL ` 防止列拥有一个空的或为定义的值。
840+ 通过 ` Mapped[t] ` 定义的字段默认会有 ` NOT NULL ` 约束,如果需要允许空值,则使用 ` Mapped[Optional[t]] ` 。
841+
842+ 下面是添加约束的 ` Product ` 类:
843+
844+ ``` Python
845+ from typing import Optional
846+ from sqlalchemy import String
847+
848+ class Product (Model ):
849+ __tablename__ = ' products'
850+
851+ id : Mapped[int ] = mapped_column(primary_key = True )
852+ name: Mapped[str ] = mapped_column(String(64 ), index = True , unique = True )
853+ manufacturer: Mapped[str ] = mapped_column(String(64 ), index = True )
854+ year: Mapped[int ] = mapped_column(index = True )
855+ country: Mapped[Optional[str ]] = mapped_column(Sting(32 ))
856+ cpu: Mapped[Optional[str ]] = mapped_column(String(32 ))
857+
858+ def __repr__ (self ):
859+ return f ' Product( { self .id} , " { self .name} ") '
860+ ```
861+
862+ ## Deletions
863+
864+ 在之前已经介绍了通过 ` add() ` 来添加项,我们也可以通过 ` delete() ` 删除项。
865+
866+ ``` Python
867+ session = Session()
868+ p = session.get(Product, 23 )
869+ session.delete(p)
870+ session.commit()
871+ ```
872+
873+ 一旦该删除被提交,将无法恢复
874+
875+ ``` Python
876+ p = session.get(Product, 23 )
877+ print (p) # None
878+ ```
879+
880+ ## Exercises
881+
882+ 1 . 1983 年的前 3 个字母表排序的结果
883+
884+ ``` Python
885+ from sqlalchemy import select
886+
887+ with Session() as session:
888+ query = select(Product).where(Product.year == 1983 ).order_by(Product.name).limit(3 )
889+ products = session.scalars(query).all()
890+
891+ for p in products:
892+ print (p)
893+ ```
894+
895+ 2 . 含有 "Z80" 的 CPU
896+
897+ ``` Python
898+ with Session() as session:
899+ query = select(Product).where(Product.cpu.like(' %Z80%' ))
900+ # query = select(Product).where(Product.cpu.contains('Z80'))
901+ products = session.scalars(query).all()
902+
903+ for p in products:
904+ print (p)
905+ ```
906+
907+ 3 . 1990 年前,CPU 含有 "Z80" 或 "6502" 的产品,并按名称字母表排序
908+
909+ ``` Python
910+ with Session() as session:
911+ query = (
912+ select(Product)
913+ .where(
914+ or_(Product.cpu.contains(' Z80' ), Product.cpu.contains(' 6502' )),
915+ Product.year < 1990
916+ )
917+ .order_by(Product.name)
918+ )
919+ products = session.scalars(query).all()
920+
921+ for p in products:
922+ print (p)
923+ ```
924+
925+ 4 . 在 1980s 年代有产品的制造商
926+
927+ ``` Python
928+ with Session() as session:
929+ query = (
930+ select(Manufacturer)
931+ .join(Manufacturer.products)
932+ .distinct()
933+ .where(Product.year >= 1980 , Product.year < 1990 )
934+ )
935+ manufacturers = session.scalars(query).all()
936+
937+ for m in manufacturers:
938+ print (m)
939+ ```
940+
941+ 5 . 名称以 T 字母开头的生产商名称,并按照字母表排序
942+
943+ ``` Python
944+ with Session() as session:
945+ query = select(Manufacturer).where(Manufacturer.name.startswith(' T' )).order_by(Manufacturer.name)
946+ manufacturers = session.scalars(query).all()
947+
948+ for m in manufacturers:
949+ print (m)
950+ ```
951+
952+ 6 . 在 Croatia 制造产品的最早、最晚的年份,以及产品数量
953+
954+ ``` Python
955+ from sqlalchemy import func
956+
957+ with Session() as session:
958+ query = (
959+ select(
960+ func.min(Product.year),
961+ func.max(Product.year),
962+ func.count()
963+ )
964+ .where(Product.country == ' Croatia' )
965+ )
966+ results = session.execute(query).one()
967+
968+ print (results)
969+ ```
970+
971+ 7 . 每年发布的产品:结果应从产品数量最多的开始排序,没有产品的年份应该忽略
972+
973+ ``` Python
974+ with Session() as session:
975+ query = (
976+ select(Product.year, func.count())
977+ .group_by(Product.year)
978+ .order_by(func.count().desc())
979+ )
980+ results = session.execute(query).all()
981+
982+ for year, count in results:
983+ print (year, count)
984+ ```
985+
986+ 8 . 国家在 USA 的生产商
987+
988+ ``` Python
989+ with Session() as session:
990+ query = (
991+ select(Manufacturer)
992+ .join(Manufacturer.products)
993+ .where(Product.country == ' USA' )
994+ .distinct()
995+ )
996+ manufacturers = session.scalars(query).all()
997+
998+ for m in manufacturers:
999+ print (m)
1000+ ```
0 commit comments