SQLAlchemy_建表(一对一/一对多/多对多)关系
Basic Relationship Patterns
基本关系模式
The imports used for each of the following sections is as follows:
下列的 import 语句,应用到接下来所有的代章节中:
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
One To Many
A one to many relationship places a foreign key on the child table referencing the parent.
表示一对多的关系时,在子表类中通过 foreign key (外键)引用父表类。
relationship() is then specified on the parent, as referencing a collection of items represented by the child:
然后,在父表类中通过 relationship() 方法来引用子表的类:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
# 在父表类中通过 relationship() 方法来引用子表的类集合
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
# 在子表类中通过 foreign key (外键)引用父表的参考字段
To establish a bidirectional relationship in one-to-many, where the “reverse” side is a many to one,
在一对多的关系中建立双向的关系,这样的话在对方看来这就是一个多对一的关系,
specify an additional relationship() and connect the two using the relationship.back_populates parameter:
在子表类中附加一个 relationship() 方法,并且在双方的 relationship() 方法中使用 relationship.back_populates 方法参数:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="children")
# 子表类中附加一个 relationship() 方法
# 并且在(父)子表类的 relationship() 方法中使用 relationship.back_populates 参数
Child will get a parent attribute with many-to-one semantics.
这样的话子表将会在多对一的关系中获得父表的属性
Alternatively, the backref option may be used on a single relationship() instead of using back_populates:
或者,可以在单一的 relationship() 方法中使用 **backref **参数来代替 back_populates 参数:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", backref="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
One To One
One To One is essentially a bidirectional relationship with a scalar attribute on both sides.
一对一是两张表之间本质上的双向关系。
To achieve this, the uselist flag indicates the placement of a scalar attribute instead of a collection on the “many” side of the relationship.
要做到这一点,只需要在一对多关系基础上的父表中使用 uselist 参数来表示。
To convert one-to-many into one-to-one:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child = relationship("Child", uselist=False, back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="child")
To convert many-to-one into one-to-one:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.id'))
child = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent = relationship("Parent", back_populates="child", uselist=False)
As always, the relationship.backref and backref() functions may be used in lieu of the relationship.back_populates approach; to specify uselist on a backref, use the backref() function:
同样的,可以使用下面这种方式:
from sqlalchemy.orm import backref
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.id'))
child = relationship("Child", backref=backref("parent", uselist=False))
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
Many To Many
Many to Many adds an association table between two classes.
多对多关系会在两个类之间增加一个关联的表。
The association table is indicated by the secondary argument to relationship().
这个关联的表在 relationship() 方法中通过 secondary 参数来表示。
Usually, the Table uses the MetaData object associated with the declarative base class,
通常的,这个表会通过 **MetaData **对象来与声明基类关联,
so that the ForeignKey directives can locate the remote tables with which to link:
所以这个 ForeignKey 指令会使用链接来定位到远程的表:
# 多对多关系中的两个表之间的一个关联表
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table)
# 在父表中的 relationship() 方法传入 secondary 参数,其值为关联表的表名
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
For a bidirectional relationship, both sides of the relationship contain a collection.
双向关系中,两个表类都会包含这个集合。
Specify using relationship.back_populates, and for each relationship() specify the common association table:
指定使用 relationship.back_populates 参数,并且为每一个 relationship() 方法指定共用的关联表:
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children")
When using the backref parameter instead of relationship.back_populates, the backref will automatically use the same secondary argument for the reverse relationship:
当在父表类的 relationship() 方法中使用 backref参数代替 relationship.back_populates 时,backref 会自动的为子表类加载同样的 secondary 参数。
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
The secondary argument of relationship() also accepts a callable that returns the ultimate argument,
secondary 参数还能够接收一个可调函数的最终返回值,
which is evaluated only when mappers are first used. Using this, we can define the association_table at a later point, as long as it’s available to the callable after all module initialization is complete:
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=lambda: association_table,
backref="parents")
With the declarative extension in use, the traditional “string name of the table” is accepted as well, matching the name of the table as stored in Base.metadata.tables:
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary="association",
backref="parents")
SQLAlchemy 连表查询
一:外键存在的意义:
任何的数据都可以在一个表中存储,但是这样存储有这个问题:如果一个字段在一个表里多次出现,而且这个字段的长度比较大,那么将会在存储上有浪费。
这个时间如果出现另一张表,存储他们之间的关系,是不是更好呢?
但是如果这样做,还会出现一个问题,比如:A B 2张表中,A中存储的时候数字ID 和B表中的ID对应相应的字段,如果这个时候在插入B表不存在的ID,这样我们
就会造成一个问题:我们不清楚这个A表中这个ID 代表什么?诸如此类的问题:最后引入外键。
外键保证了A表中所有的对应类型的ID 都是B表中的存在的数字ID 也就是唯一性约束。如果B 关系表中不存在的ID,在A表插入的时候,会插入失败,并报错。
1)外键是mysql一种特殊的索引。创建了2个表的关系对应。
2)建立了唯一性约束。
问题:这几天测试外键的约束性,一直不成功,最后找到原因。因为使用的mysql的版本很低,默认的存储引擎是MyISAM。
1 mysql> show engines;
2 +------------+---------+------------------------------------------------------------+--------------+------+------------+
3 | Engine | Support | Comment | Transactions | XA | Savepoints |
4 +------------+---------+------------------------------------------------------------+--------------+------+------------+
5 | MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
6 | CSV | YES | CSV storage engine | NO | NO | NO |
7 | MyISAM | YES | Default engine as of MySQL 3.23 with great performance | NO | NO | NO |
8 | InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
9 | MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
10 +------------+---------+------------------------------------------------------------+--------------+------+------------+
11 5 rows in set (0.00 sec)
1 mysql> select @@version;
2 +-----------+
3 | @@version |
4 +-----------+
5 | 5.1.73 |
6 +-----------+
7 1 row in set (0.01 sec)
引擎:MyISAM不支持外键约束。所以修改数据默认引擎。直接修改配置文件。在mysql配置文件(linux下为/etc/my.cnf),在mysqld后面增加default-storage-engine=INNODB即可。
重启mysql既可。
然后创建外键就有外键约束了。坑!!!!
二:SQLALchemy
注意SQLALchemy是通过类和对象创建创建相应的表结构。插入的数据也类的对象。
1 class User(Base):
2 __tablename__="user"#这个是创建的表的名字。
3 nid=Column(Integer,primary_key=True,autoincrement=True)
4 name=Column(String(12))
5 group_id=Column(Integer,ForeignKey("group.group_id"))#注意ForeignKey是类 初始化对象。而不是等于。注意创建的外键里添加的字符串是表格名字不是类的名字!!! 6
7 class Group(Base): 8 __tablename__="group"
9 group_id=Column(Integer,primary_key=True) 10 name=Column(String(12))
上面的代码有问题:如果我们想设置主键的话,最好不要设置我们自己的值。最好设置单独一列做为主键要不然插值的时候报错。
sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, "Duplicate entry '1' for key 'PRIMARY'") [SQL: 'INSERT INTO group_1 (group_id, name) VALUES
一:单表查询
进行单表查询的时候,查询的结果返回的是类的一个对象:
1 from sqlalchemy.ext.declarative import declarative_base
2 from sqlalchemy import Column, Integer, String, ForeignKey
3 from sqlalchemy.orm import sessionmaker,relationship
4 from sqlalchemy import create_engine
5
6 engine = create_engine("mysql+pymysql://root:@192.168.1.104:3306/day13", max_overflow=5)
7 Base = declarative_base()
8 class User(Base)
9 __tablename__ = 'user'
10 nid = Column(Integer, primary_key=True,autoincrement=True)
11 username = Column(String(32))
12 group_id = Column(Integer,ForeignKey('cc.nid'))
13 Session = sessionmaker(bind=engine)
14 session = Session()
15 ret=session.query(User).filter(User.username=="alex1").all()
16 print(ret)
17 [<__main__.User object at 0x0344B390>]
对对象进行相应的操作:
1 ret=session.query(User).filter(User.username=="alex1").all()
2 print(ret[0].username)
3 alex1
根据之前学习的,当我们print输出一个对象默认是调用该对象的一个str方法。但是在SQLALchemy里 规定 调用的是repr方法,返回值是什么,在打印对象的时候就输出什么。
我们可以自定义repr方法来,重定向我们输出的结果,方便我们在操作表的时候,进行输出。
1 class User(Base):
2 __tablename__ = 'user'
3 nid = Column(Integer, primary_key=True,autoincrement=True)
4 username = Column(String(32))
5 group_id = Column(Integer,ForeignKey('cc.nid'))
6 gruop=relationship("Group",backref="cc")
7 def __repr__(self):
8 result=('%s-%s')%(self.username,self.group_id)
9 return result
10 ret=session.query(User).filter(User.username=="alex1").all() 11 print(ret) 12 [alex1-1]
二:一对多,多表查询:
表结构:
image.png
如果进行多表查询的时候,原生sql如下:
1 mysql> select * from cc join user on user.group_id=cc.nid;
2 +-----+---------+-----+----------+----------+
3 | nid | caption | nid | username | group_id |
4 +-----+---------+-----+----------+----------+
5 | 1 | dba | 1 | alex1 | 1 |
6 +-----+---------+-----+----------+----------+
7 1 row in set (0.00 sec)
在sqlalchemy里默认帮你把on后面的操作进行了。
1 ret=session.query(Group).join(User)
2 print(ret)
3 SELECT cc.nid AS cc_nid, cc.caption AS cc_caption FROM cc JOIN "user" ON cc.nid = "user".group_id
`
1 class Group(Base):
2 __tablename__ = 'cc'
3 nid = Column(Integer, primary_key=True,autoincrement=True)
4 caption = Column(String(32))
5 def __repr__(self):
6 result=('%s-%s')%(self.nid,self.caption)
7 return result
8
9 ret=session.query(Group).join(User).all()
10 print(ret)
11 [1-dba]
如上是inner joner,在sqlalchemy里没有right join只有left join
1 mysql> select * from cc left join user on user.group_id=cc.nid;
2 +-----+---------+------+----------+----------+
3 | nid | caption | nid | username | group_id |
4 +-----+---------+------+----------+----------+
5 | 1 | dba | 1 | alex1 | 1 |
6 | 2 | ddd | NULL | NULL | NULL |
7 +-----+---------+------+----------+----------+
8 2 rows in set (0.00 sec)
left join:isouter=True。
1 ret=session.query(Group).join(User,isouter=True).all()
2 print(ret)
如果想使用right join的话 把类颠倒下即可。
ret=session.query(User).join(Group,isouter=True).all()
print(ret)
如果连表查询的结果都是对User里的user表的操作,我们需要时Group里的表的内容。可以进行如下操作,在query()里添加我们想要操作的表对应的类。
1 ret=session.query(User,Group).join(Group).all()
2 sql=session.query(User,Group).join(Group)
3 print(ret)
4 print(sql)
5 [(alex1-1, 1-dba)]
6 SELECT "user".nid AS user_nid, "user".username AS user_username, "user".group_id AS user_group_id, cc.nid AS cc_nid, cc.caption AS cc_caption
7 FROM "user" JOIN cc ON cc.nid = "user".group_id
上面默认是把Group的cc表里的caption=User.group_id里的所有数据输出。
如果只想要对应的字段可以query()里指定想要的字段:
1 ret=session.query(User.username,Group.caption).join(Group).all()
2 sql=session.query(User,Group).join(Group)
3 print(ret)
4 print(sql)
5 [('alex1', 'dba')]
6 SELECT "user".nid AS user_nid, "user".username AS user_username, "user".group_id AS user_group_id, cc.nid AS cc_nid, cc.caption AS cc_caption
7 FROM "user" JOIN cc ON cc.nid = "user".group_id
relationship 查询:
如上的操作对于SQLALchemy来说,还是有些麻烦,于是就就有:relationship()来方便我们进行查询。他只是方便我们查询,对表结构无任何影响。
在哪个表里设置外键,一般就在那个表里设置关系(relationship),这样我们就可以进行更为简单的查询。
1 class Group(Base):
2 __tablename__ = 'cc'
3 nid = Column(Integer, primary_key=True,autoincrement=True)
4 caption = Column(String(32))
5
6 class User(Base): 7 __tablename__ = 'user'
8 nid = Column(Integer, primary_key=True,autoincrement=True)
9 username = Column(String(32))
10 group_id = Column(Integer,ForeignKey('cc.nid'))
11 group=relationship("Group",backref="user")
12 Session = sessionmaker(bind=engine)
13 session = Session()
14 sql=session.query(User)
15 print(sql)
16 SELECT "user".nid AS user_nid, "user".username AS user_username, "user".group_id AS user_group_id FROM "user"
1 ret=session.query(User).all()
2 for i in ret:
3 print(i.group.caption)
4 dba
5 ddd
如上的查询是正向查询。
1 ret=session.query(Group).filter(Group.caption=="dba").first()
2 print(ret.user)
3 for i in ret.user:
4 print(i.username,i.group_id)
5 [<__main__.User object at 0x0349C850>]
6 alex1 1
如上是反向查询。
说明:
image.pngcolumn_name=relationship("B表名","B表新添加虚拟列")
column_name是表A的为了查询建立的虚拟的列。实际表结构中不存在这个列。这个列是B的对象的集合。可以通过这个列获取B表的中相应的列的值。如上表。
1 res=session.query(User).all()
2 print(res)
3 for i in res:
4 print(i.group.caption)
relationship("B表名","B表新添加虚拟列")中的"B表新添加虚拟列",我简称为B列。也就是说B表添加一个虚拟的列B,虚拟B列是A表的对象集合。通过B列可以查询出A表的值。
1 ret=session.query(Group).all()
2 print(ret)
3 for i in ret:
4 for j in i.user:
5 print(j.username)
6 alex1
7 alex2
注意是2个for循环。因为ret是Group的对象列表,而列表中的对象的user列是User的对象集合。所以进行2次循环。
总结:relationship是方便查询,在一对多;表结构中分别创建了一个虚拟关系列,方便查询。
三:多对多:表查询
结构:多对多关系中,最简单的是由三张表组成。第三张表是关系表。其他表和这张关系表的关系是一对多的关系。
image.png
表A和表B通过第三张表C建立关系。
创建多对多表结构:
1 from sqlalchemy.ext.declarative import declarative_base
2 from sqlalchemy import Column, Integer, String, ForeignKey
3 from sqlalchemy.orm import sessionmaker,relationship
4 from sqlalchemy import create_engine
5
6 engine = create_engine("mysql+pymysql://root:@192.168.1.105:3306/s12", max_overflow=5)
7 Base = declarative_base()
8
9 class System_user(Base):
10 __tablename__="system_user"
11 username=Column(String(30))
12 nid=Column(Integer,autoincrement=True,primary_key=True)
13
14
15 class Host(Base):
16 __tablename__="host"
17 nid=Column(Integer,autoincrement=True,primary_key=True)
18 ip=Column(String(30))
19
20
21 class SystemuserToHost(Base):
22 __tablename__="systemusertohost"
23 nid=Column(Integer,autoincrement=True,primary_key=True)
24 sys_user_id=(Integer,ForeignKey("system_user.nid"))
25 host_id=Column(Integer,ForeignKey("host.nid"))
26
27 Base.metadata.create_all(engine)
28
29
30 mysql> show tables;
31 +------------------+
32 | Tables_in_s12 |
33 +------------------+
34 | host |
35 | system_user |
36 | systemusertohost |
37 +------------------+
38 3 rows in set (0.00 sec)
插入数据:
1 def add_user():
2 session.add_all(
3 (System_user(username="evil"),
4 System_user(username="tom"),
5 System_user(username="root"),
6 System_user(username="admin"),
7
8 )
9 )
10 session.commit()
11 def add_host():
12 session.add_all(
13 (Host(ip="172.17.11.12"),
14 Host(ip="172.17.11.13"),
15 Host(ip="172.17.11.14"),
16 Host(ip="172.17.11.15"),
17 )
18 )
19 session.commit()
20
21 def add_systemusertohost():
22 session.add_all(
23 (SystemuserToHost(sys_us_id=1,host_id=1),
24 SystemuserToHost(sys_us_id=2,host_id=1),
25 SystemuserToHost(sys_us_id=3,host_id=1),
26 SystemuserToHost(sys_us_id=1,host_id=2),
27 SystemuserToHost(sys_us_id=1,host_id=3),
28 SystemuserToHost(sys_us_id=2,host_id=4),
29
30 )
31 )
32 session.commit()
33
34 add_user()
35 add_host()
36 add_systemusertohost()
需求:ip=172.17.11.12 的主机上的用户都有什么?
按之前的查询:
1 ret_2=session.query(Host.nid).filter(Host.ip=="172.17.11.12").first()
2 print(ret_2[0])
3 ret=session.query(SystemuserToHost.sys_us_id).filter(SystemuserToHost.host_id==ret_2[0]).all()
4 for i in ret: 5 print(i)
6 list_user=zip(*ret)#ret=((1,),(2,),(3))将ret转换成(1,2,3)的迭代器。
7
8 list_user=list(list_user)[0]#转换成列表。
9
10 ret_1=session.query(System_user.username).filter(System_user.nid.in_(list_user)).all() 11 print(ret_1)
1 1
2 (1,) 3 (2,) 4 (3,) 5 [('evil',), ('tom',), ('root',)</pre>
1)首先需要从Host中找指定IP=172.17.11.12 的对应nid。
2)从SystemuserToHost中找到对应的user_id
3)然后从System_user中找到对应的用户列表。
方法一:relationship建立在关系表中:
建立查询关系(relationship):
1 class SystemuserToHost(Base):
2 __tablename__="systemusertohost"
3 nid=Column(Integer,autoincrement=True,primary_key=True)
4 sys_us_id=Column(Integer,ForeignKey("system_user.nid"))
5 sys=relationship("System_user",backref="uu")
6 host_id=Column(Integer,ForeignKey("host.nid"))
7 host=relationship("Host",backref="host")
8
9
10 Session=sessionmaker(bind=engine)
11 session=Session()
12 ret=session.query(Host).filter(Host.ip=="172.17.11.12").first()
13 print(ret.host)#生成SystemuserToHost的对象集合。然后通过sys列找到username。
14 for i in ret.host:
15 print(i.sys.username)
16 evil
17 tom
18 root
思想:通过第三张关系C表和其他两张表建立外键,然后通过关系表和其他两张表建立关系(relationship),A表通过建立查询关系虚拟列A,映射到关系表虚拟列C,虚拟列C中包含B表的对象集合,直接映射到想要得到的B表的列值。
image.png二:relationship建立在表A中:
1 from sqlalchemy.ext.declarative import declarative_base
2 from sqlalchemy import Column, Integer, String, ForeignKey
3 from sqlalchemy.orm import sessionmaker,relationship
4 from sqlalchemy import create_engine
5
6 engine = create_engine("mysql+pymysql://root:@192.168.1.105:3306/s12", max_overflow=5)
7 Base = declarative_base()
8
9 class System_user(Base):
10 __tablename__="system_user"
11 nid=Column(Integer,autoincrement=True,primary_key=True)
12 username=Column(String(30))
13
14
15
16 class Host(Base):
17 __tablename__="host"
18 nid=Column(Integer,autoincrement=True,primary_key=True)
19 ip=Column(String(30))
20 host_u=relationship("System_user",secondary=lambda:SystemuserToHost.__table__,backref="h")#注意需要写通过那个表(secondary=lambda:SystemuserToHost.__table__)和System_user建立关系。注意secondary=后面跟的是对象。如果没有lambda需要把类SystemuserToHost写在前面。
21
22 class SystemuserToHost(Base):
23 __tablename__="systemusertohost"
24 nid=Column(Integer,autoincrement=True,primary_key=True)
25 sys_us_id=Column(Integer,ForeignKey("system_user.nid"))
26 host_id=Column(Integer,ForeignKey("host.nid"))
27
28
29
30 Session=sessionmaker(bind=engine)
31 session=Session()
32 ret=session.query(Host).filter(Host.ip=="172.17.11.12").first()
33 for i in ret.host_u:
34 print(i.username) 35 evil 36 tom 37 root
注意需要写通过那个表(secondary=lambda:SystemuserToHost.table)和System_user建立关系。注意secondary=后面跟的是对象。如果没有lambda需要把类SystemuserToHost写在前面。SystemuserToHost未定义。
image.png
网友评论