SQLAlchemy
以上,SQLAlchemy的两个最重要的前端部分是ORM(Object Relational Mapper,,即对象关系映射器)和 SQL表达式语言。SQL表达式可以独立于ORM使用。使用ORM时,SQL表达式语言仍然是面向对象的API的一部分,因为它在对象关系配置和查询中使用。
SQLAlchemy是python中的ORM框架。对象关系映射(英语:Object Relation Mapping,简称ORM,或O/RM,或O/R mapping)其他语言中都会有ORM框架,可以进行更加快捷便利的操作数据库。
SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
注意:SQLAlchemy本身无法操作数据库,必须依赖第三方插件如pymysql,mysqlDB,Dialect用于和数据API进行交流,根据配置文件的不同调用不同数据库的API,从而实现对数据库的操作。
一,创建表
默认的情况下SQLAlchemy不能修改表结构,可以下载第三方工具进行修改。
#!/usr/bin/python # _*_ coding:utf-8 _*_ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) # 创建连接 Base = declarative_base() # 规定的必须要写这一步 # ------------------创建单表----------------------- class Users(Base): #必须继承Base __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(32)) extra = Column(String(16)) __table_args__ = ( # 创建的联合索引 UniqueConstraint('id', 'name', name='uix_id_name'), Index('ix_id_name', 'name', 'extra'), ) # -----------------一对多----------------------- class Favor(Base): __tablename__ = 'favor' nid = Column(Integer, primary_key=True) caption = Column(String(50), default='red', unique=True) class Person(Base): __tablename__ = 'person' nid = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=True) favor_id = Column(Integer, ForeignKey("favor.nid")) # -----------------多对多------------------ class Group(Base): __tablename__ = 'group' id = Column(Integer, primary_key=True) name = Column(String(64), unique=True, nullable=False) port = Column(Integer, default=22) class Server(Base): __tablename__ = 'server' id = Column(Integer, primary_key=True, autoincrement=True) hostname = Column(String(64), unique=True, nullable=False) class ServerToGroup(Base): # 要想创建多对多的关系,需要创建第三个表保存他们的对应关系 __tablename__ = 'servertogroup' nid = Column(Integer, primary_key=True, autoincrement=True) server_id = Column(Integer, ForeignKey('server.id')) group_id = Column(Integer, ForeignKey('group.id')) def init_db(): Base.metadata.create_all(engine) # 找到Base所有的子类,根据类去创建表 def drop_db(): Base.metadata.drop_all(engine) # init_db() # 创建所有的表 drop_db() # 删除所有的表
二,操作表
#!/usr/bin/env python from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) Base = declarative_base() # 创建单表 class Users(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(32)) extra = Column(String(16)) __table_args__ = ( UniqueConstraint('id', 'name', name='uix_id_name'), Index('ix_id_name', 'name', 'extra'), ) def __repr__(self): # 这个方法是SQLAlchemy内部自己规定的,这个函数对查询没有一点用处,只是在打印对象时方便查看 return "%s-%s" %(self.id, self.name) # 一对多 class Favor(Base): __tablename__ = 'favor' nid = Column(Integer, primary_key=True) caption = Column(String(50), default='red', unique=True) def __repr__(self): return "%s-%s" %(self.nid, self.caption) class Person(Base): __tablename__ = 'person' nid = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=True) favor_id = Column(Integer, ForeignKey("favor.nid")) # 与生成表结构无关,仅用于查询方便 favor = relationship("Favor", backref='pers') # 多对多 class ServerToGroup(Base): __tablename__ = 'servertogroup' nid = Column(Integer, primary_key=True, autoincrement=True) server_id = Column(Integer, ForeignKey('server.id')) group_id = Column(Integer, ForeignKey('group.id')) group = relationship("Group", backref='s2g') server = relationship("Server", backref='s2g') class Group(Base): __tablename__ = 'group' id = Column(Integer, primary_key=True) name = Column(String(64), unique=True, nullable=False) port = Column(Integer, default=22) # group = relationship('Group',secondary=ServerToGroup,backref='host_list') class Server(Base): __tablename__ = 'server' id = Column(Integer, primary_key=True, autoincrement=True) hostname = Column(String(64), unique=True, nullable=False) def init_db(): Base.metadata.create_all(engine) def drop_db(): Base.metadata.drop_all(engine) Session = sessionmaker(bind=engine) session = Session() # ------增-------- obj = Users(name="alex0", extra='sb') session.add(obj) session.add_all([ # 添加数据 Users(name="alex1", extra='sb'), Users(name="alex2", extra='sb'), ]) session.commit() # 提交 # -------删-------- session.query(Users).filter(Users.id > 2).delete() session.commit() # -------改--------- session.query(Users).filter(Users.id > 2).update({"name": "099"}) session.query(Users).filter(Users.id > 2).update({Users.name: Users.name + "099"}, synchronize_session=False) # 字符串的相加 session.query(Users).filter(Users.id > 2).update({"num": Users.num + 1}, synchronize_session="evaluate") # 数字的相加,synchronize_session="evaluate",这样设置之后就会把值当成数字进行相加。 session.commit() # --------查----------- ret = session.query(Users).all() # 查询所有,此时返回的是User对象列表 ret = session.query(Users.name, Users.extra).all() # 查询两列 ret = session.query(Users).filter_by(name='alex').all() # 过滤出name='alex'的所有 ret = session.query(Users).filter_by(name='alex').first() # 只取第一条 # 条件 ret = session.query(Users).filter_by(name='alex').all() ret = session.query(Users).filter(Users.id > 1, Users.name == 'eric').all() # 是'且'的关系 ret = session.query(Users).filter(Users.id.between(1, 3), Users.name == 'eric').all() ret = session.query(Users).filter(Users.id.in_([1,3,4])).all() ret = session.query(Users).filter(~Users.id.in_([1,3,4])).all() ret = session.query(Users).filter(Users.id.in_(session.query(Users.id).filter_by(name='eric'))).all() from sqlalchemy import and_, or_ ret = session.query(Users).filter(and_(Users.id > 3, Users.name == 'eric')).all() ret = session.query(Users).filter(or_(Users.id < 2, Users.name == 'eric')).all() # 这里是'或'的关系 ret = session.query(Users).filter( or_( Users.id < 2, and_(Users.name == 'eric', Users.id > 3), Users.extra != "" )).all() # 通配符 ret = session.query(Users).filter(Users.name.like('e%')).all() ret = session.query(Users).filter(~Users.name.like('e%')).all() # 限制 ret = session.query(Users)[1:2] # 相当于mysql中的limit # 排序 ret = session.query(Users).order_by(Users.name.desc()).all() ret = session.query(Users).order_by(Users.name.desc(), Users.id.asc()).all() # 按照name排序,name相同再按照id排序 # 分组 from sqlalchemy.sql import func ret = session.query(Users).group_by(Users.extra).all() ret = session.query( func.max(Users.id), func.sum(Users.id), func.min(Users.id)).group_by(Users.name).all() ret = session.query( func.max(Users.id), func.sum(Users.id), func.min(Users.id)).group_by(Users.name).having(func.min(Users.id) >2).all() # 连表 ret = session.query(Users, Favor).filter(Users.id == Favor.nid).all() ret = session.query(Person).join(Favor).all() # 默认是join类型为inner join,自动连表(在内部会找foreignkey) ret = session.query(Person).join(Favor, isouter=True).all() # isouter=True表示的是left join # 组合 q1 = session.query(Users.name).filter(Users.id > 2) q2 = session.query(Favor.caption).filter(Favor.nid < 2) ret = q1.union(q2).all() # 默认去重 q1 = session.query(Users.name).filter(Users.id > 2) q2 = session.query(Favor.caption).filter(Favor.nid < 2) ret = q1.union_all(q2).all() # 默认不去重
三,详解一对多
group
nid | caption |
1 | 1 |
2 | 2 |
user
nid | username | goup_id |
1 | lintao | 1 |
2 | lily | 1 |
3 | lucy | 1 |
创建上述表并对表进行简单的操作
#!/usr/bin/env python # _*_ coding:utf-8 _*_ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) Base = declarative_base() # 固定用法 class Group(Base): __tablename__ = 'group' nid = Column(Integer, primary_key=True, autoincrement=True) caption = Column(String(32)) class User(Base): __tablename__ = 'user' nid = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(32)) group_id = Column(Integer, ForeignKey('group.nid')) #(引擎必须是InnoDB)创建外键,对两张表进行关联 # 注意mysql的引擎从5.5.5开始默认引擎是InnoDB, 之前的版本默认是MyISAM引擎,MyISAM不支持外键,此时需要 # 修改配置文件/etc/my.cnf 在mysqld下面增加一行:default-storage-engine=INNODB,然后重启即可生效。 Base.metadata.create_all(engine) # 创建表 Base.metadata.drop_all(engine) Session = sessionmaker(bind=engine) # 相当于保持一个会话 session = Session() # 创建group表记录 session.add(Group(caption='dba')) # 记录逐条添加 session.add(Group(caption='dbd')) session.commit() #必须提交,否则不会生效 # 创建user表记录 session.add_all([ # 一次性添加多条记录 User(username='lin', group_id=1), User(username='lily', group_id=2), User(username='lucy', group_id=3) # 此时如果group表中的nid没有为3的,那么此时这条记录就会报错!这就是外键的作用 ]) session.commit() # 对表进行操作 sql = session.query(User).filter(User.username == "tom") # 返回的是SQL语句 print(sql) ret = session.query(User).filter(User.username == "tom").all() # 返回的是一个对象的列表 # 如果返回的对象不知道是什么的话,可以在SQLAlchemy可以用repr方法进行自定制 print(ret) ret1 = session.query(User.username).filter(User.username == "tom").all() # 返回的是username这一列,中的'tom' print(ret1) # 连表查询,这里不用像SQL语句中还要加on字段,这里由SQLAlchemy会在内部自动查找foreignkey sql_1 = session.query(User).join(Group) sql_2 = session.query(User).join(Group, isouter=True) ret2 = session.query(User).join(Group).all() print(sql_1) # 通过sql语句可以看出默认的是inner join print(sql_2) # 通过添加isouter = True, 可以更改为left join print(ret2) # 进行关联查询所有的数据
小结:
1,创建表需要指定外键
2,类, repr方法(# 这个方法是SQLAlchemy内部自己规定的,这个函数对查询没有一点用处,只是在打印对象时方便查看)
3,单表
4,连表 session.query("表一").join("表二").all()
上面我们发现查询在查询的时候只有user表,能不能查到group表呢?
ret3 = session.query(User, Group).all() # 返回的是对象 ret4 = session.query(User.username, User.nid, Group.caption, Group.nid).all() #返回的是列 print(ret3) print(ret4)
下面针对上述表我们提出一个需要:如何获取caption为‘dba’所有的用户名?
第一种实现方式
#!/usr/bin/env python # _*_ coding:utf-8 _*_ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) Base = declarative_base() class Group(Base): __tablename__ = 'group' nid = Column(Integer, primary_key=True, autoincrement=True) caption = Column(String(32)) class User(Base): __tablename__ = 'user' nid = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(32)) group_id = Column(Integer, ForeignKey('group.nid')) group = relationship("Group", backref='pers') # 与表结构无关,仅用来查询 # relationship("类名") def __repr__(self): # 这里只是来用打印对象时有用,对于查询无用 tmp = "%s -- %s :%s" % (self.nid, self.username, self.group_id) return tmp Session = sessionmaker(bind=engine) # 相当于保持一个会话 session = Session()
第二种实现方式
其中第二种实现方式,有分为两种,我们姑且称之为:正向查找和反向查找
由于连表操作比较麻烦,SQLAlchemy又提供了一个新的功能。在声明外键(ForeignKey)的表中增加一个新的字段,group = relationship("Group", backref='pers'),一般情况下foreikey和relationship在一张表里面。这一功能和Django中的类似。到底这个字段代表什么含义呢?
1,group字段含义是什么?(正向查找,通过带有关系(relationship)的表向其他地方找)
#通过user表来查询 ret = session.query(User).all() for obj in ret: # obj 代指user表中的每一行数据 # obj.group 代表的是由Group实例化的对象(对象封装的东西和user相关) print(obj.username, obj.group_id, obj.nid) print(obj.group, obj.group.nid, obj.group.caption) # SQLAlchemy在内部做的关联 print("----------------")
2,backref=pers字段是什么含义?(反向查找,通过其他表,来找到带有relation的表)
# 通过group表来查询 obj = session.query(Group).filter(Group.caption == 'dba').first() print(obj.nid) print(obj.caption) print(obj.pers) #注意这里是反向查找,此时的obj.pers代表的是User实例化对象(对象封装的内容和group相关),和正向恰好相反,结合 正向查找去理解
注意pers在数据库中并没有真实的表与其对应,只是SQLAlchemy在内存中生成的SQL语句而已。就相当于在group表中增加了一个pear字段,就像表user中的group字段一样。
四,表创建过程
1,先把写好的类转换为table对象,
2,然后根据table对象生成SQL语句
注意:在python中并没有严格的限制,可以直接用table对象生成SQL语句,一般正式和标准的用法都会用类来生成。在其他语言中只允许用类生成表。
五,详解多对多
server
server_id | server_name | port | ip |
1 | c1 | xx | xxx.xx.xx.x |
2 | c2 | xx | xx.xx.xx.xx |
3 | c3 | xxxx | xx.xxx.xx.x |
server_user
user_id | user_name |
1 | tom |
2 | lily |
3 | lucy |
一台主机可以有多个用户,一个用户又可以同时存在多个主机,这就是多对多的关系,这时需要建立第三张关系表
relation_table
sid | uid |
1 | 1 |
1 | 3 |
2 | 2 |
3 | 2 |
3 | 1 |
此时还需要对第三张表建立约束:外键,这样可以控制数据的一致性和完整性
创建表结构并插入数据
#!/usr/bin/env python # _*_ coding:utf-8 _*_ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) Base = declarative_base() class Host(Base): __tablename__ = 'host' nid = Column(Integer, primary_key=True, autoincrement=True) hostname = Column(String(32)) port = Column(String(32)) ip = Column(String(32)) class HostUser(Base): __tablename__ = 'host_user' nid = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(32)) class HostToHostUser(Base): __tablename__ = 'host_to_host_user' nid = Column(Integer, primary_key=True, autoincrement=True) # 暂时保留主键 host_id = Column(Integer, ForeignKey('host.nid')) host_user_id = Column(Integer, ForeignKey('host_user.nid')) # Base.metadata.create_all(engine) # 创建表 Session = sessionmaker(bind=engine) session = Session() # 添加host表中数据 session.add_all([ Host(hostname='c1', port='22', ip='1.1.1.1'), Host(hostname='c2', port='22', ip='1.1.1.2'), Host(hostname='c3', port='22', ip='1.1.1.3'), Host(hostname='c4', port='22', ip='1.1.1.4'), Host(hostname='c5', port='22', ip='1.1.1.5'), ]) session.commit() # 添加host_user表中数据 session.add_all([ HostUser(username='root'), HostUser(username='db'), HostUser(username='nb'), HostUser(username='sb'), ]) session.commit() # 添加host_to_host_user表中数据 session.add_all([ HostToHostUser(host_id=1, host_user_id=1), HostToHostUser(host_id=1, host_user_id=2), HostToHostUser(host_id=1, host_user_id=3), HostToHostUser(host_id=2, host_user_id=2), HostToHostUser(host_id=2, host_user_id=4), HostToHostUser(host_id=2, host_user_id=3), ]) session.commit()
需求:如何获取主机1中所有的用户?
方案一:传统方式
思路:
1,先通过主机1的主机名在host表,获取主机的nid
2,在通过主机的nid,在host_to_host_user表中找到对应的host_user_id,为用户的ID
3,最后在host_user表中找到和用户id相对应的用户的信息
host_obj = session.query(Host).filter(Host.hostname == 'c1').first() # print(host_obj.nid) # 1 host_2_host_user = session.query(HostToHostUser.host_user_id).filter(HostToHostUser.host_id == host_obj.nid).all() # print(host_2_host_user) # [(1,), (2,), (3,)] # 如何把它转换为列表,或者元组, # >>> li # [('a',), ('b',), ('c',)] # >>> r = map(lambda x:x[0], li) # >>> print(list(r)) # ['a', 'b', 'c'] # 也可以用r = zip(*host_2_host_user)--> list(r[0])此时为元组('a', 'b', 'c') users = session.query(HostUser.username).filter(HostUser.nid.in_(map(lambda x: x[0], host_2_host_user))).all() print(users) # 注意这里fliter不但支持==,还支持in的操作 执行结果: [('root',), ('db',), ('nb',)]
方案二:新方式(常用)
A
AB ==> foreign_key relationship
B
!/usr/bin/env python # _*_ coding:utf-8 _*_ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) Base = declarative_base() class Host(Base): __tablename__ = 'host' nid = Column(Integer, primary_key=True, autoincrement=True) hostname = Column(String(32)) port = Column(String(32)) ip = Column(String(32)) class HostUser(Base): __tablename__ = 'host_user' nid = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(32)) class HostToHostUser(Base): __tablename__ = 'host_to_host_user' nid = Column(Integer, primary_key=True, autoincrement=True) # 暂时保留主键 host_id = Column(Integer, ForeignKey('host.nid')) host_user_id = Column(Integer, ForeignKey('host_user.nid')) host = relationship("Host", backref='h') # host_user = relationship('HostUser', backref='u') # # 增加上面两个字段,多对多就相当于两个一对多 # Base.metadata.create_all(engine) # 创建表 Session = sessionmaker(bind=engine) session = Session() # 第三张表对应的对象 host_obj = session.query(Host).filter(Host.hostname == 'c1').first() print(host_obj.nid, host_obj.hostname, host_obj.port) print(host_obj.h) # 此时的h代表的HostToHostUsers实例化的对象(和Host相关的) # 循环获取第三张表对应的对象 for item in host_obj.h: # print(item.host_user) # 获取HostUser实例化的和关系表相关的内容 r = item.host_user.username print(r) 思路:先通过反向查找,找到HostToHostUser(即关系表)实例化的对象,然后再通过这个对象获取host_user这个字段,这个字段即代表HostUser(即关系表)对象,此时正向查找到相对应的host表中的uername
方案三:新方式(不常用,不太合理) 对象+类的方法创建的
A ==> relationship
AB ==> foreign_key
B
这里的关系表和foreign_key没有放在一张表里面
#!/usr/bin/env python # _*_ coding:utf-8 _*_ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index, Table #这里需要导入Table模块 from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) Base = declarative_base() class HostUser(Base): __tablename__ = 'host_user' nid = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(32)) HostToHostUser = Table('host_to_host_user', Base.metadata, Column('host_id', ForeignKey('host.nid'), primary_key=True), Column('host_user_id', ForeignKey('host_user.nid'), primary_key=True), ) class Host(Base): __tablename__ = 'host' nid = Column(Integer, primary_key=True, autoincrement=True) hostname = Column(String(32)) port = Column(String(32)) ip = Column(String(32)) host_user = relationship("HostUser", secondary=HostToHostUser, backref='h') # 注意这里的secondary为关系表的名字:HostToHostUser Session = sessionmaker(bind=engine) session = Session() host_obj = session.query(Host).filter(Host.hostname == 'c1').first() # print(list(map(lambda x: x.username, host_obj.host_user))) for item in host_obj.host_user: print(item.username)
方案四:也比较合理,简单
#!/usr/bin/env python # _*_ coding:utf-8 _*_ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index, Table from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@10.1.1.3:3306/s13", max_overflow=5) Base = declarative_base() class HostUser(Base): __tablename__ = 'host_user' nid = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(32)) class HostToHostUser(Base): __tablename__ = 'host_to_host_user' nid = Column(Integer, primary_key=True, autoincrement=True) # 暂时保留主键 host_id = Column(Integer, ForeignKey('host.nid')) host_user_id = Column(Integer, ForeignKey('host_user.nid')) class Host(Base): __tablename__ = 'host' nid = Column(Integer, primary_key=True, autoincrement=True) hostname = Column(String(32)) port = Column(String(32)) ip = Column(String(32)) host_user = relationship("HostUser", secondary=HostToHostUser.__table__, backref='h') # 注意这里的secondary为:HostToHostUser, __table__为获取的table对象 Session = sessionmaker(bind=engine) session = Session() host_obj = session.query(Host).filter(Host.hostname == 'c1').first() # print(list(map(lambda x: x.username, host_obj.host_user))) for item in host_obj.host_user: print(item.username)
SQLAlchemy总结:
1,orm自动生成表,然后根据类和对象去生成表
2,创建表,简单
3,操作表(其实就是转换SQL语句),需要多练。
单表
连表
right.join, left join, inner join
关系:
一对多(一般情况下外键和关系会放到一起,foreignkey,relationship)
多对多
多一张表,放的是foreignkey,关联约束
1,关系表:放关系
2,A:放关系 ,(B, AB)
练习的顺序:从上往下练习,能用SQL就用SQL,像涉及到一对多和多对多时,SQLAlchemy比较简单的再考虑用。