外键:
MySQL通过外键约束来保证表与表之间的数据的完整性和准确性。
1、字段指定了外键,则字段值必须在外键字段值中。
2、作为外键的字段,值必须唯一,可以使用primary_key或unique约束键值唯一。
3、指定外键的字段和被指定外键的字段,字段类型必须相同或相似。
指定外键的方法:
在Column中添加ForeignKey('表名.字段名')
# 指定外键:from sqlalchemy import create_engin, Column, String, Integer, ForeignKeyfrom sqlalchemy.ext.declarative import declarative_baseengin = create_engin('mysql+pymysql://david:Yaotiao&shunv666@192.168.2.120/test',encoding='utf-8')Base = declarative_base()class Aroduct(Base): __tablename__ = 'aroducts' id = Column(Integer, primary_key=True) name = Column(String(32),unique=True) # 作为外键,使用unique保证唯一 price = Column(Integer) def __repr__(self): return "name:{},price:{}".format(self.name,self.price)class Broduct(Base): __tablename__ = 'broducts' id = Column(Integer, primary_key=True) name = Column(String(32), ForeignKey('aroducts.name')) # 添加外键Base.metadata.create_all(engine) # 创建两个表
向aroducts批量添加数据:
def add_data(product_name): product_list = [] for i in range(90,100): product = Aroduct(name = product_name+str(i),price = i) product_list.append(product) session.add_all(product_list)add_data('华为')# 添加后,查看aroducts数据库里的数据是:'''mysql> select * from aroducts;+----+----------+-------+| id | name | price |+----+----------+-------+| 1 | 华为90 | 90 || 2 | 华为91 | 91 || 3 | 华为92 | 92 || 4 | 华为93 | 93 || 5 | 华为94 | 94 || 6 | 华为95 | 95 || 7 | 华为96 | 96 || 8 | 华为97 | 97 || 9 | 华为98 | 98 || 10 | 华为99 | 99 |+----+----------+-------+'''
向broducts添加数据:需要导入一个sessionmaker
from sqlalchemy.orm import sessionmakerSession = sessionmaker(bind = engin)session =Session() # b1 = Broduct(name = 'huawei') # huawei不在上面表的name中,所以会报错b1 = Broduct(name = '华为91')session.add(b1)# 添加后查询结果mysql> select * from broducts;+----+----------+| id | name |+----+----------+| 1 | 华为91 |+----+----------+
relationship:
使用时,需要导入relationship模块
指定关联关系,实现两表之间字段互访。指定两表relationship时,两表之间必须存在ForeignKey关系。
relationship不会改变Mysql的结构和属性,只是在relationship中查询使用,所以即使在表生成后,也可以添加relationship.
只要存在ForeignKey关系的两个表,relationship可以加到任意表里。
格式: 字段1 = relationship('外部表class对象', secondary = '关联表名', backref='字段2')
说明:
字段1:本表通过“字段1”,查询外部表的字段
外部表Class对象:sqlalchemy映射的表类
secondary:多对多关系中,生成的三方表。
backref:反向查询字段名,相当于在外部表添加一个字段,外部表通过这个字段查询本表字段。
lazy: 1. 默认值为select, 他直接会导出所有的结果对象合成一个列表
2. dynamic,他会生成一个继承与Query的AppenderQuery对象,可以用于继续做过滤操作。 当需要对映射的结果集继续筛选的时候,可以在relationship指定lazy参数为'dynamic' 3. 其他的还有很多参数,例如joined,连接查询,但是涉及到查询性能
from sqlalchemy.orm import relationshipclass Broduct(Base): __tablename__ = 'broducts' id = Column(Integer, primary_key=True) name = Column(String(32), ForeignKey('aroducts.name')) # 添加外键 query_a = relationship('Aroduct', backref='query_b') # 在Broduct ORM对象里添加a 字段,定义relationship # 通过Broduct查询Aroduct中的price值b = session.query(Broduct).filter(Broduct.name=='华为91').first() # print(b.query_a.price) # 调用query_a,查询aroducts表里的 price的值'''# 显示结果91'''# 通过通过Aroduct查询Broduct中的idb = session.query(Aroduct).filter(Aroduct.name=='华为91').first() # 如果是all(),以列表显示,不能直接使用b.属性调用数据。print(b.query_b[0].id) # 因为使用ForeignKey的一方,系统默认为是多的一方,# 所以反向查询关联字段query_b时,得到的是一个List,所以查询值时要加上索引# 使用all()all_show = session.query(Aroduct).filter(Aroduct.name=='华为91').all() # 如果是all(),以列表显示,不能直接使用b.属性调用数据。print(all_show[0].query_b[0].id) '''1'''
sqlalchemy关系模型:
1、一对多
一个班级可以有很多学生
class Grade(Base): __tablename__ = 'grades' id = Column(Integer) name = Column(String(32)) identifier = Column(String(64),primary_key=True) def __repr__(self): return 'id:{},name:{},identifier:{}'.format(self.id, self.name,self.identifier) class User(Base): __table__ = 'users' id = Column(Integer) name = Column(String(32)) qq = Column(String(64),primary_key=True) grade_id = Column(String(64), ForeignKey('grades.identifier')) grade = relationship('Grade', backref = 'user') def __repr__(self): return 'id:{},name:{},qq:{},grade_id:{},grade:{}'.format(self.id,self.name,self.qq,self.grade_id)
2、一对多(多外键关联)
当一个表里多个字段关联同一个外键。
所有用户数据都放在teachers表里,一个班级(grades表)需要一个班主任,和一个讲师。
这样出现了当前grades表的不同字段,外键关联到teacher表的同一个字段。
class Grade(Base): __tablename__ = 'grades' id = Column(Integer) name = Column(String(32)) identifier = Column(String(64),primary_key=True) # class_teacher和lecturer指定同一个字段做为外键。 class_teacher = Column(String(64), ForeignKey('teachers.qq')) lecturer = Column(String(64),ForeignKey('teachers.qq')) # 正常使用relationship创建查询关系 # teacher = relationship('Teacher') # v_lecturer = relationship('Teacher') # 截止以上的语句,表结构可以创建完成 # 但是添加数据时会提示:多外键关联错误,参照本文后面的“错误提示-6” # 所以,如果要创建查询关系,给relationship加上参数,作用是指定每个查询对应的外键字段 teacher = relationship('Teacher', foreign_keys=[class_teacher]) v_lecurer = relationship('Teacher', foreign_keys=[lecturer]) # 这样再向teachers表里插数据就正常了 def __repr__(self): return 'id:{},name:{},identifier:{}'.format(self.id, self.name,self.identifier) class Teacher(Base): __table__ = 'teachers' id = Column(Integer) name = Column(String(32)) qq = Column(String(64),primary_key=True) # 设定为主键 def __repr__(self): return 'id:{},name:{},qq:{},grade_id:{},grade:{}'.format(self.id,self.name,self.qq)
3、多对多
现在系统要求升级了,不仅班级里有多个学生,一个学生可以报多个班级
# 多对多需要使用中间表,中间表需要导入Table类。# 使用ondelete='CASCADE'参数,可以避免删除有关联的项。from sqlalchemy import Table# 1、创建一个中间表,外键为grades表和user表的主键。grd_to_user = Table('grd_to_user', Base.metadata, Column('id',Integer,primary_key = True), Column('grd_identifier',String(64),ForeignKey('grades.identifier')) Column('user_qq',String(64),ForeignKey('users.qq')) )class Grade(Base): __tablename__ = 'grades' id = Column(Integer) name = Column(String(32)) identifier = Column(String(64),primary_key=True) # 创建关联关系,secondary='grd_to_user'指向中间表。 user = relationship('User',secondary='grd_to_user',backref='grd') class User(Base): __tablename__ = 'users' id = Column(Integer) name = Column(String(32)) qq = Column(String(64),primary_key=True) # 2、插入数据:# 中间表的数据会自动生成,不需要手动添加。# grades表和users表数据添加方法。grd1 = Grade(name='Python基础','Python-20181225')grd2 = Grade(name='Linux基础','Linux-20181225')grd3 = Grade(name='Docker基础','Docker-20181225')user1 = User(name='王大拿','112233')user2 = User(name='赵四','112244')user3 = User(name='刘能','112255')user4 = User(name='刘大脑袋','112266')user5 = User(name='谢广坤','112277')grd1.user = [user1,user2,user3]grd2.user = [user1,user2,user3,user4,user5]grd3.user = [user3,user4,user5]# 创建表结构Base.metadata.create_all(engin)# 把所有数据添加到数据库session.add_all([grd1,grd3,grd2,user1,user2,user3,user4,user5])# 确认提交保存session.commit()# 3、查询数据:# 查询'王大拿'报了哪几个班?search_user1 = session.query(User).filter(User.name == '王大拿').all()# 结果是对象列表,显示:for i in search_user1: # 从查询结果列表里读取对象名 for j in i.grd: # 利用relationship的backref反查字段,查询课程名 print(j.name) # 显示结果'''Linux基础 Python基础''' '''Mysql中查询方法:mysql> select grades.* from users inner join grd_to_user on users.qq = grd_to_user.user_qq inner join grades on grd_to_user.grd_identifier = grades.identifier and users.name='王大拿';+------+--------------+-----------------+| id | name | identifier |+------+--------------+-----------------+| NULL | Linux基础 | Linux-20181225 || NULL | Python基础 | Python-20181225 |+------+--------------+-----------------+2 rows in set (0.00 sec)'''
常见错误提示:
# 1、sqlalchemy.exc.InternalError: (pymysql.err.InternalError) (1822, "Failed to add the foreign key constraint. Missing index for constraint 'broducts_ibfk_1' in the referenced table 'aroducts'")
# 指定的外键不是另一个表里的primary_key或unique
# 2、sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'broducts.name' could not find table 'Products' with which to generate a foreign key to target column 'name'
# 外键表名错误
# 3、sqlalchemy.exc.NoReferencedColumnError: Could not initialize target column for ForeignKey 'aroducts.ssd' on table 'broducts': table 'aroducts' has no column named 'ssd'
# 外键的字段名错误
# 4、sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1215, 'Cannot add foreign key constraint')
# 外键与本字段数据类型不一致
# 也有可能数据库里面存在一名字一样的表,这个表的字段类型与新建表不一样
# 5、sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1216, 'Cannot add or update a child row: a foreign key constraint fails')
# 向表里存数据,因为有外键关联,但存的数据在外键中没有
# 6、sqlalchemy.exc.ProgrammingError: (pymysql.err.ProgrammingError) (1146, "Table 'test.aroducts' doesn't exist")
# 存数据的时候提示,找不到表名,数据库里没有这张表
# 7、sqlalchemy.exc.InvalidRequestError: Table 'user' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
解决方法:在所有的models里面添加:__table_args__ = {"useexisting": True}或删除Base.matedata
#8、sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, "Duplicate entry '192.168.2.120' for key 'ip'") [SQL: 'INSERT INTO hostinfo (ip, hostname, port) VALUES (%(ip)s, %(hostname)s, %(port)s)'] [parameters: {'ip': '192.168.2.120', 'hostname': 'bbshost1', 'port': 8800}] (Background on this error at:
原因是有重复输入的IP了,这句是主要的。1062, "Duplicate entry '192.168.2.120' for key 'ip'"
查看表结构定义的是不是有问题,unique和primarykey字段的值都是唯一的,我是因为ip和port字段做联合唯一,写错了。
应该在__table_args__里定义联合唯一。
需要注意的是,如果已在mysql里创建表了,那么,不仅要改变sqlalchemy table的字段定义,还必须要改变mysql表的字段定义。或者重新创建表。
参考文章:
多对多: