关于sqlalchemy:sqlalchemey-关键核心问题

关系解决一对多关系解决 关系定义实例 # Node 表node = relationship('Node', back_populates='task', foreign_keys=[node_id], primaryjoin='Task.node_id == Node.id')# Task 工作表task = relationship('Task', back_populates='node', primaryjoin='foreign(Task.node_id) == Node.id')可只用back_ref 参数,Task.task 属性就无需定义;示例中没有应用ForeignKey, 因而须要指明primaryjoin 属性,指定关系;

March 4, 2022 · 1 min · jiezi

ORM: SQLAlchemy 初识

ORM: Object Relational Mapper.目前Python有很多ORM工具可以将数据库映像为Python的Objects对象。其中比较知名的有Django的ORM,SQLAlchemy, PostgreSQL等。SQLAlchemy有更多的人维护,功能也比较齐全。所以一般是我们的首选项。对于SQLAlchemy的使用者来说,只要你一开始连接上数据库,不管是Sqlite,MySQL还是什么,后面的处理方式完全一样。这种便利性也是它受欢迎的原因。抛弃了传统的自己编织SQL语句、制作模型、连接数据库方式,SQLAlchemy直接把这些东西全包在黑盒里面,让我们完全不需要去管。连SQL-Injection注入这种东西也被它帮忙防范了。这样一来,可以说在连接数据库方面,帮我们节省了最少一半以上的代码。甚至连数据查询,SQLAlchemy也代替了SQL语句,而使用了专门的类似MongoDB的Object.query.filter_by(name=‘Jason’).all()这种方法。安装:# 安装sqlalchemy$ pip install sqlalchemy安装Drivers:# Sqlite# 不需要,Python自带# MySQL$ pip install pymysql# Postgresql$ pip install psycopg2SQLAlchemy自身不带数据库driver,需要我们自己安装,并在连接时候指定。而这些driver,实际上就是我们曾经手动连接数据库所用的包。而SQLAlchemy只是代替我们使用这些同样的包。连接数据库创建一个sqlite的ORM引擎:from sqlalchemy import create_engine# 连接格式为:sqlite://<Hostname>/<path>engine = create_engine(‘sqlite:///foo.db’, echo=True)创建一个MySQL的ORM引擎:from sqlalchemy import create_engine# 连接格式为:dialect+driver://username:password@host:port/databaseengine = create_engine(‘mysql+pymysql://root:password123@localhost/db_test_01’, echo=True)数据库的位置(三斜杠为相对路径,四斜杠为绝对路径):# 使用绝对路径的数据库文件(////),如/tmp/mydatabase.dbengine = create_engine(‘sqlite:////tmp/mydatabase.db’)# 使用当前「执行位置」数据库文件(///或///./)engine = create_engine(‘sqlite:///mydatabase.db’)# 使用当前「执行位置」父级目录(///../)的数据库文件engine = create_engine(‘sqlite:///../mydatabase.db’)# 使用当前「脚本位置」的数据库文件import oscwd = os.path.split(os.path.realpath(file))[0]engine = create_engine(‘sqlite:///{}/mydatabase.db’.format(cwd))Create Tables 创建表注意:不同于SQL语句,SQLAlchemy中的表名是完全区分大小写的!创建一个Schema表(指单纯表,不包含ORM对象):from sqlalchemy import create_engine, MetaDatafrom sqlalchemy import Table, Columnfrom sqlalchemy import Integer, String, ForeignKeyengine = create_engine(‘mysql+pymysql://root:password123@localhost/db_test_01’, echo=True)metadata = MetaData(engine)# 创建一个表user_table = Table( ’tb_user’, metadata, Column(‘id’, Integer, primary_key=True), Column(’name’, String(50)), Column(‘fullname’, String(100)))# 让改动生效metadata.create_all()创建一个ORM对象(包括表):# 导入表格创建引擎from sqlalchemy import create_engine# 导入列格式from sqlalchemy import Column, Integer, String, ForeignKey# 导入创建ORM模型相关from sqlalchemy.ext.declarative import declarative_baseBase = declarative_base()class User(Base): tablename = ’tb_Person’ id = Column(‘id’, Integer, primary_key=True) username = Column(‘username’, String, unique=True)engine = create_engine(‘sqlite:///test.sqlite’, echo=True)Base.metadata.create_all(bind=engine)用普通表Table和ORM对象创建的表有什么不同?他们在数据库中创建的,是完全相同的表!唯一区别是,Table创建的不包含ORM对象,也就是不提供让你直接操作Python对象的功能。这么做的好处是,有很多只是关联作用的表,没有必要生成ORM对象。删除数据库中的表# engine = …# Base = …# 逐个ORM对象删除对应的表,如User类User.table.drop(engine)# 删除全部表Base.metadata.drop_all(engine)设计或调试过程中,我们经常要频繁改动表格,所以有必要在创建表格前把测试数据库中的表都清除掉,再创建新的定义。Insertion 插入数据将数据添加到数据库:# …# 导入session相关(用于添加数据)from sqlalchemy.orm import sessionmaker, relationshipuser = User()user.id = 1user.username = ‘Jason’Session = sessionmaker(bind=engine)session = Session()session.add(user)session.commit()session.close()注意:这里的session和网站上的session概念有点不一样。这里是用来commit提交数据库变动的工具。批量添加数据(向add_all()传入列表):session.add_all( [user1, user2, user3] )添加每条数据的时候自动flush():session = sessionmaker(bind=engine, autoflush=True)autoflush是在每次session.add()自动执行session.flush(),即在插入数据库之前就在内存中生成所有对象的动态数据(如主键ID等)。一般默认是选false,因为会影响效率。最好是需要的时候,才手动执行session.flush()具体缘由,看下一节“数据生效”。Take effect 数据生效SQLAlchemy中的create_all()和session.commit()都是直接让python文件中定义的对象在数据库中生效的语句。在此之前,无论怎么定义,数据都是在内存中,而没有在数据库中的。注意区分:create_all只是让创建表格结构生效,无关insert的数据条目session.commit()只是让添加的数据生效,而不负责任何表格结构。这两个的顺序,当然是先创建表格,再插入数据。只是,如果我们知道了这个原理,在编码中才能比较运用自由。比如,连create_engine()创建引擎,我们都可以在后面定义,而没必要非得写在文件头,即所有的ORM定义之前。create_engine只要定义在所有ORM类和Schema表之后即可。此后,我们再开始进行数据插入工作,也就利用到了session。session过程中呢,我们也会遇到互相引用主键外键ID的情况。但是注意,这时候因为还没有使用最终的session.commit()真正提交数据到数据库中,这些ID是没有值的。解决办法就是利用内置的方法session.flush(),将session中已添加的所有对象填充好数据,但是这时候还没有提交到数据库,只是我们内部可以正常访问各种ID了。更新/删除数据更新:# Get a row of datame = session.query(User).filter_by(username=‘Jason’).first()# Method 1:me.age += 1session.commit()# Method 2:session.query().filter( User.username == ‘Jason’).update( {“age”: (User.age +1)})session.commit()# Method 3:setattr(user, ‘age’, user.age+1)session.commit()Get Primary Key Value 获取主键值#sqlalchemy can’t get primary key , #sqlalchemy 如何获得主键的值这个问题花了我很多时间探索查询,不得其解,才明白原来是很显然的事。参考思否:SQLAlchemy中返回新插入数据的id?虽然在没有用session或engine插入数据之前,我们可以直接浏览从ORM创建的对象中的属性值。但是这个时候无论如何都获取不到primar_key主键列的值。因为这时候主键还没有插入数据库,作为动态的值,在数据库没生效之前也就为None。为什么需要获取value of primary_key?考虑如下这些场景:子表中的foreign key外键需要引用主表的id??那么该怎么获取主键ID呢?再参考Stackoverflow:sqlalchemy flush() and get inserted id?再参考:sqlalchemy获取插入的id再参考:Sqlalchemy;将主键设置为预先存在的数据库表(不使用sqlite)如果要想在插入数据之前就获取主键等动态列的值,那么有这几种方法:直接利用SQLAlchemy建立类直接的内部关联,而不直接使用ID主表插入数据,另session生效后,再用query获取相应对象,来得到它的ID。(*) 主表先用session.add(..),再session.flush(),然后就可以获取ID,最后再session.commit()不使用primary key主键,自己手动创建ID,这样来随便获取。推荐做法如下:即每次新创建对象后,立刻session.add(..),然后立刻session.flush(),全部都添加好的文末,再session.commit().Query 查询注意:query是通过session进行的,也就是必须在session.commit()之后才能进行查询,否则会报错。这里将的query查询,指的都是在插入到数据库生效之后。理解这个很重要,因为在对象未插入到数据库之前,很多主键、外键等内容都是不存在的,也就无法查询到。参考:pythonsheets - Object Relational basic query查询数据:session.commit()# …users = session.query(User).all()# 返回的是多个User类的对象:>>> [ <User 1>, <User 2>, …. ]for u in users: print(u.id, u.username)常用查询方法:# 获取某ORM中数据 .query(ORM类名)>>> session.query( User ).all() # All rows of data>>> session.query( User ).first() # First row of data as an object# 查询结果排序 .order_by(类名.列名)>>> session.query(User).order_by( User.birth ).all()# 筛选结果 .filter( True/False 表达式 )>>> session.query(User).filter( User.name != ‘Jason’ ).all()>>> session.query(User).filter( User.name.like(’%ed%’) ).all() # Fuzzy search>>> session.query(User).filter( User.id in [1, 2, 3] ).all() # IN>>> session.query(User).filter( ~ User.id in [4, 5, 6] ).all() # NOT IN>>> session.query(User).filter( User.school == ‘MIT’, User.age < 24 ).first() # AND>>> session.query(User).filter( _or(User.school == ‘MIT’, User.age < 24) ).first() # OR ...

January 24, 2019 · 2 min · jiezi

深究SQLAlchemy中的表关系 Table Relationships

SQL中的表关系一直是比较难理解的地方。同样SQLAlchemy也对他们做了实现,如果对SQL中的表关系理解透彻的话,这里也可以更容易理解。为什么需要定义Relationships在相关联的表中,我们可以不创建表关联的定义,而只是单纯互相引用id即可。但是,查询和使用起来就要麻烦很多:#给定参数User.name,获取该user的addresses# 参考知乎:https://www.zhihu.com/question/38456789/answer/90470689def get_addresses_from_user(user_name): user = session.query(User).filter_by(name=user_name).first() addresses = session.query(Address).filter_by(user_id=user.id).all() return addresses可以看到,这样的效率非常低。好在原生的SQL就有relationship设置,SQLAlchemy将其引入到了ORM模型中。它可以让我们只在表中声明表之间的关系,之后每次使用就完全无需手动交叉搜索,而是像对待一个表中的数据一样直接使用。为什么不需要定义relationships?经过实践返回来加的这一节:实践中的SQLAlchemy的"relationship"在一定程度上反而导致了整体表关联关系的极大复杂化,还有效率的极其低下。如果你的数据库只有两个表的话,那么relationship随便定义随便用。如果只有几百条数据的话,那么也请随便玩。但是,当数据库中有数十个表以上,单个关联层级就多过三个表以上层层关联,而且各个数据量以万为单位。那么,“relationship"会把整个人都搞垮,简直还不如手写SQL语句清晰好理解,并且效率也差在了秒级与毫秒级的区别上。SQLAlchemy只能很轻松handle Many to Many,但是如果是常见的Many to Many to Many,或者是Many to Many to Many to Many,那简直就是噩梦。但是,我们都知道,项目做到一定程度,都会摆脱不了ORM。无论是自己造轮子还是用别人的,无论起点是不是纯SQL,终点都是ORM。那么该怎么办呢?网友的建议是:用SQLAlchemy建立各种ORM类对象,不要用内置的关联,直接在查询的时候手动SQL语句!经过实践,我的建议是:容易SQL-Injection注入的地方,用SQLAlchemy的query创建ORM对象时候,用SQLAlchemy多层关联的时候,不要用SQLAlchemy查询的时候,用SQL插入数据的时候,不要用SQLAlchemy。(官方都说明了插入百万级的时候,和SQL插件是秒级的)relationship() 函数参考官方文档:Linking Relationships with BackrefSQLAlchemy创建表关联时,使用的是relationshi()这个函数。它返回的是一个类的属性,比如father类的children属性。但是,它实际上并没有在father表中创建任何叫children的列,而是自动帮你到相关联的children表中去找数据,让你用起来感觉没有差别而已。这是非常方便的!relationship()这个函数的参数非常多,每一个参数都有很多内容需要理解。因为所有的表关联的形态,都是在这个函数里面定义的。以下分别讲解。Reference 正向引用传统的方法,是在父类中定义一个关系 relationship或叫正向引用 Reference,子类只需定义一个外键。比如:class Father(..): id = Column(..) children = relationship(‘Child’)class Child(..): father_id = Column( Integer, ForeignKey(‘father.id’) )# 添加数据daddy = Father()jason = Child()emma = Child()# 将孩子挂到父亲名下daddy.children.append(jason)daddy.children.append(emma)这样当每次我们使用father.children的时候,就会自动返回与这个father相关联的所有children了。Back Reference 反向引用单纯定义的relationship(‘子类名’)只是一个正向引用,也就是只能让父类调用子对象。反过来,如果要问children他们的父亲是谁,就不行了。所以,我们还需要一个反向引用 (Back Reference)的声明,让子对象能够知道父对象是谁。定义方式是在父类的relationship(..)中加一个参数backref:class Father(..): children = relationship( ‘Child’, backref=‘parent’ )注意:backref参数里面使用的随便写,主要用于之后子类的引用。backref参数是双向性的,意思是,只需要在父类中声明一次,那么父⇄子的双向关系就确立了,不用再去子类中写一遍。这时候,我们在添加就可以这样互相调用了:>>> Jason = Child()>>> print( Jason.parent ) <main.Father object at 0x10222f860>Bidirectional & Unidirectional Back Reference 双向和单向的反向引用后来,SQLAlchemy发现这种只在一边定义双向性backref的方法有点不太直观,所以又添加了另一个参数back_populates参数,而这个back_populates参数是单向性的,也就是说:你要确立双方向关系就必须在两边的类中都声明一遍。这样比较直观。可以把backref和back_populates都读为"as”,这样就好记忆了。比如:class Father(..): id = Column(..) children = relationship( ‘Child’, back_populates=‘parent’ )class Child(..): father_id = Column( Integer, ForeignKey(‘father.id’) ) parent = relationship( ‘Father’, back_populates=‘children’ )注意:back_populates要求父类子类的关系名称必须严格“对称”:父类的relationship属性名children,必须对应子类的关系中的back_populates中的值子类的relationship属性名parent,必须对应父类的关系中的back_populates中的值这样一来利用反向引用参数创建的关系就确立了。但是注意,无论用backref还是back_populates创建的关联,如果我们必须要为父子对象添加对象间的关联才能引用,否则谁也不知道谁是谁的父亲、儿子:>>> daddy = Father()>>> son = Child()>>> daughter = Child()>>> daddy.children[]>>> son.parentNone>>> daddy.children.append( son )>>> daddy.children.append( daughter )>>> daddy.children[ <Child …>, <Child …> ]>>> son.parent<Father …>另外:上面添加父子关系的时候,不光可以用daddy.children.append,还可以在声明子对象的时候确定:son = Child( parent=daddy )反向引用参数对比:backref参数:双方向。在父类中定义即可。只能通过daddy.children.append()方式添加子对象关联。back_populates参数:单方向。必须在父子类中都定义,且属性名称必须严格对称。还可以通过Child(parent=daddy)的方式添加父对象关联。SQL中的表关系对应关系:One to Many 一对多:Many to One 多对一:Many to Many 多对多:One to Many 一对多建立一个One-to-Many的多表关联:# …class Person(Base): id = Column(…) name = Column(…) pets = relationship(‘Pet’, backref=‘owner’) # 上面这句是添加一关联,而不是实际的列 # 注意:1. ‘Pet’是大写开头,因为指向了Python类,而不是数据库中表 # 2. backref是指建立一个不存在于数据库的“假列”, # 用于添加数据时候指认关联对象,代替传统id指定class Pet(Base): id = Column(…) name = Column(…) owner_id = Column(Integer, ForeignKey(‘person.id’) # 上面这句添加了一个外键, # 注意外键的’person’是数据库中的表名,而不是class类名,所以用小写以区分创建好关联的表以后,我们就可以直接插入数据了。注意,插入带关联的数据也和SQL插入有些不同:#…# 添加主人andy = Person(name=‘Andrew’)session.add( andy )seession.commit()# 添加狗pp01 = Pet(name=‘Puppy’, owner=andy)pp02 = Pet(name=‘Puppy’, owner=andy)# 注意这句话中,owner是刚才主表中注册relationship中的backref指定的参数名,# 传给owner的是主表的一个Python实例化对象,而不是什么id# 看起来复杂,实际上sqlalchemy可以自动取出object的id然后匹配副表中的foreignkey。session.add(pp01)session.add(pp02)session.commit()print( andy.pets )# >>> [<Pet 1>, <Pet, 2>]# 返回的是两个Pet对象print( pp01.owner )# >>> <Person ‘Andrew’># 同样,副表中利用owner这个backref定义的假列,返回的是Person对象。Many to One 多对一比如职工和公司的关系就是多对一。这和公司与职工对一对多有什么区别?区别其实是在SQL语句中的:多对一的关联关系,是在多的一方的表中定义,一的一方表中没有任何关系定义:class Company(…): id = Column(…)class Employee(..): id = Column(…) company_id = Column( …, ForeignKey(‘company.id’) ) company = relationship(“Company”)Many to Many 多对多多对多的关系也很常见,比如User和Radio的关系:一个Radio可以有多个用户可以订阅,一个用户可以订阅多个Radio。SQL中处理多对多的关系时,是把多对多分拆成两个一对多关系。做法是:新创建一个表,专门存储映射关系。原本的两个表无需设置任何外键。SQLAlchemy的实践中,也和SQL中的做法一样。注意:既然有了专门的Mapping映射表,那么两个表各自就不需要注册任何ForeignKey外键了。示例:# 做出一个专门的表,存储映射关系# 注意:1. 这个表中两个"id"都不是主键,因为是多对多的关系,所以二者都可以有多条数据。# 2. 映射表必须在前面定义,否则后面的类引用时,编译器会找不到radio_users = Table(‘radio_users’, Base.metadata, Column(‘whatever_name1’, Integer, ForeignKey(‘radios.id’)), Column(‘whatever_name2’, Integer, ForeignKey(‘users.id’)))# 定义两个ORM对象:class Radio(Base): tablename = ‘radios’ rid = Column(‘id’, Integer, primary_key=True) followers = relationship(‘User’, secondary=radio_users, # secondary是专门用来指明映射表的 back_populates=‘subscriptions’ # 这个值要对应另一个类的属性名 )class User(Base): tablename = ‘users’ uid = Column(‘id’, Integer, primary_key=True) subscriptions = relationship(‘Radio’, secondary=radio_users, back_populates=‘followers’ # 这个值要对应另一个类的属性名 )其中,secondary是专门用来指明映射表的。注意:多对多的时候我们也可以用backref参数来添加互相引用。但是这种方法太不直观了,容易产生混乱。所以这里建议用back_populates参数,在两方都添加引用,表现一种平行地位,方便理解。然后插入数据时候是这么用:r1 = Radio()r2 = Radio()r3 = Radio()u1 = User()u2 = User()u3 = User()# 添加对象间的关联r1.followers += [u1, u2, u3]# 反过来添加也一样u1.subscriptions += [r2, r3]Many to Many to Many 多对多对多 (深层关联)深层关联,为了避免理解困难,最笨的方法就是简单的使用外键ID,然后手动搜索另一个表的对应ID。但是SQLAlchemy也可以实现这种深层关联: ...

January 24, 2019 · 2 min · jiezi

SQLAlchemy进阶:Lazy Load 加载参数

参考:flask-sqlalchemy中的lazy的解释SQLAlchemy的relationship( …, lazy=’??’ )方法中的lazy参数一直是初学最容易困扰的地方。Lazy Load Methods是SQLAlchemy为多表关联而定义的一系列加载方法。为lazy参数选择什么值,决定了 SQLAlchemy 什么时候从数据库中加载数据。每种方法的对应着SQL语句中多表关联的一种写法,所以优缺点、效率高低各有不同。lazy参数的可选方法有:select - (默认) 后台会用select语句一次性加载所有数据,即访问到属性的时候,就会全部加载该属性的数据。joined - 数据会被JOIN语句加载,即对关联的两个表进行join操作,从而获取到所有相关的对象。subquery - 数据被用subquery子查询SQL语句加载dynamic - 在访问属性的时候,并不在内存中加载数据,而是返回一个query对象, 需要执行相应方法才可以获取对象。适用于数据量大的时候。immediate - items should be loaded as the parents are loaded, using a separate SELECT statement, or identity map fetch for simple many-to-one references.noload - no loading should occur at any time. This is to support “write-only” attributes, or attributes which are populated in some manner specific to the application.True - 即 ‘select’方法False - 即 ‘joined’方法None - 即’noload’方法下面用School和Students的实例来看各种方法的不同。假设定义两个ORM类:class School(..): id = Column(..) students = relationship( ‘Student’, backref=‘school’ )class Student(..): id = Column(..) school_id = Column(.., ForeignKey(‘school.id’) )上例中我们建立了一个普通的两表关联:students = relationship( ‘Student’, backref=‘school’ )。默认情况下,参数lazy为select,我们不写也可以)。也就是说,如果定义lazy=‘select’,那么当我们要进行搜索引用时(假设表中已有数据):>>> school_01 = School.query.first() # 随便获取一个数据库中已有的school>>> school_01.students[ <Student: u’test’>, <Student: u’test2’>, <Student: u’test3’> ]可以看到,lazy=‘select’会简单直接的返回所有相关联的数据。但是,如果数据量非常大:比如百万级,这种全部返回就不理智了,因为会大量侵占内存。所以我们可以选择lazy=‘dynamic’,即只返回一个query查询对象,供你手动加条件查询,比如query.all()或query.filter()等。假设我们将之前的定义改为:students = db.relationship(‘Student’, backref=’_class’, lazy=“dynamic”)。那么:>>> school_01.students<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x7f007d2e8ed0>>>> print( school_01.students )SELECT students.id AS students_id, students.name AS students_nameFROM students, registrationsWHERE :param_1 = registrations.class_id AND students.id = registrations.student_id>>> school_01.students.all()[ <Student: u’test’>, <Student: u’test2’>, <Student: u’test3’> ]可以看到, 执行school_01.students返回的只是一个query对象,甚至说只是返回了一条SQL语句,就是没有具体数据。可以想像这个消耗的时间相当于0了。而如果lazy=select 或者 joined均是直接返回结果。 需要注意的是,lazy=“dynamic"只可以用在一对多和多对对关系中,不可以用在一对一和多对一中。这样也合理:如果返回结果很少的话,就没必要延迟加载数据了。backref(…, lazy=…) 反向引用的lazy加载直接给relationship(.., lazy=’??’),只是给正向引用设置加载方法。实际上反向引用也是可以设置lazy加载方法的。做法就是:使用backref(..)函数: students = relationship(…, lazy=’..’, backref=backref(‘Student, lazy=‘dynamic’) )可以看到,backref(..)函数返回的是一个backref参数专用的值,在这里面可以指定反向引用的加载方法。 ...

January 24, 2019 · 1 min · jiezi

SQLAlchemy的类继承、抽象类

Python中的类继承是相当容易的,但是在SQLAlchemy中却不能直接用Python类继承完成,还要多加一些设置。网上关于这个东西,东说西说的非常多,甚至官网都没有把最简单的解决方案po出来,取而代之的是非常复杂的Inheritance Configuration。首先说最简单的方案,来自Stackoverflow,亲测完美有效,最符合Python类继承。参考:Sqlalchemy: avoiding multiple inheritance and having abstract base class正解在这里,我们称这个方法为__abstract__方法:Base = declarative_base()class CommonRoutines(Base): abstract = True id = Column(Integer, primary_key=True) def init(self): # …class Foo(CommonRoutines): tablename = ‘foo’ name = Column(…) def init(self, name): super().init() self.name = name # …也就是说,抽象类中只要用__abstract__ = True代替__tablename__即可完成一切工作,其它一切都和Python内置的类继承一摸一样了。继承中的类方法和静态方法SQLAlchemy的ORM继承,在classmethod和staticmethod继承是和Python OOP面向对象的继承方案一致的。也就是说:被冠之@staticmethod的静态方法,会被继承,但是在子类调用的时候,却是调用的父类同名方法。被冠之@classmethod的类方法,会被继承,子类调用的时候就是调用子类的这个方法。继承中的外键奇怪的是,SQLAlchemy定义的ORM,在继承父级ORM时候,Foreign Key外键是不能继承的,它强制要求在子类中重新定义。参考官方文档:Mapping Class Inheritance Hierarchies 建议直接用Ctrl-f搜索"foreign`关键字,就能看到官方在继承时,也都要重新定义一遍外键。再参考:SQLAlchemy Inheritanceclass Parent(Base): abstract = True id = Column(‘id’, Integer, primary_key=True) name = Column(’name’, String) age = Column(‘age’, String) fk = Column(‘fk’, Integer, ForeignKey(‘anotherTable.id’), primary_key=True)class Son(Parent): tablename = ‘son’ fk = Column(‘fk’, Integer, ForeignKey(‘anotherTable.id’), primary_key=True)其它继承方案如果参考别人的方案、官网的方案,会让你晕头转向。为了避免重复参考别人的东西,这里贴上一些不是解决方案的解决方案。declarative_base(cls=XX)方法:class CommonBase(object): @classmethod def somecommonaction(cls): # body hereBase = declarative_base(cls=CommonBase)class Table1(Base): # tablename & Table1 specific fields hereclass Table2(Base): # tablename & Table2 specific fields here这样的缺点是,很难看清继承关系。官方的__mapper_args__方法:class Person(Base): tablename = ‘people’ id = Column(Integer, primary_key=True) discriminator = Column(’type’, String(50)) mapper_args = {‘polymorphic_on’: discriminator}class Engineer(Person): tablename = ’engineers’ mapper_args = {‘polymorphic_identity’: ’engineer’} id = Column(Integer, ForeignKey(‘people.id’), primary_key=True) primary_language = Column(String(50))可以看出,这个必须在父子类都中分别定义难懂的__mapper_args__属性。这还不算完,官网中还说各种映射需要不同的复杂设置。有兴趣可参考官网:https://docs.sqlalchemy.org/e… ...

January 24, 2019 · 1 min · jiezi

flask-sqlalchemy操作(基础)

以下内容介绍了Sqlalchemy的基础查询语句,下篇文章将介绍其高级查询(聚合、自关联、连接、子查询等)模型类# 用户表class User(db.Model): tablename = ‘user’ uid = db.Column(db.String(32), primary_key=True, nullable=False) username = db.Column(db.String(20), nullable=True) password = db.Column(db.String(128), nullable=True) email = db.Column(db.String(30), nullable=True) addresses = db.relationship(‘Address’, backref=‘user’)# 地址信息class Address(db.Model): tablename = ‘address’ aid = db.Column(db.String(32), primary_key=True, nullable=False) name = db.Column(db.String(32), nullable=True) site = db.Column(db.String(100), nullable=True) phone = db.Column(db.Integer, nullable=True) uid = db.Column(db.String(32), db.ForeignKey(‘user.uid’))# 关联表OrderItem = db.Table( ‘orderitem’, db.Column(‘gid’, db.String(32), nullable=True), db.Column(‘product_id’, db.String(32), db.ForeignKey(‘product.pid’)), db.Column(‘order_id’, db.String(32), db.ForeignKey(‘order.oid’)))# 商品信息class Product(db.Model): tablename = ‘product’ pid = db.Column(db.String(32), nullable=False, primary_key=True) pname = db.Column(db.String(50), nullable=True) market_price = db.Column(db.Float, nullable=True) shop_price = db.Column(db.Float, nullable=True) pimage = db.Column(db.String(200), nullable=True) pdate = db.Column(db.Date, nullable=True) is_hot = db.Column(db.Integer, nullable=True) pdesc = db.Column(db.String(255), nullable=True) pflag = db.Column(db.Integer, nullable=True) order = db.relationship(‘Order’, secondary=OrderItem)# 订单表class Order(db.Model): tablename = ‘order’ oid = db.Column(db.String(32), nullable=False, primary_key=True) count = db.Column(db.Integer, nullable=True) subtotal = db.Column(db.Float, nullable=True) ordertime = db.Column(db.DateTime, nullable=True) flag = db.Column(db.String(10), nullable=True)增一对一order = models.Orders(oid=orderid, ordertime=datetime.now(), total=pcount, uid=pid) # 构建对象models.db.session.add(order) # 添加对象models.db.session.commit() # 提交事务一对多p = models.User(uid=‘122’, username=‘hello’, password=‘123456’, email='1@qq.com’) # 主表c1 = models.Address(aid=‘1111111111’,site=‘xxxxxxxxxx’) # 子表c2 = models.Address(aid=‘2222222222’, site=‘yyyyyyyyyy’) # 子表p.addresses = [c1, c2] # 赋值对象models.db.session.add(p) # 添加models.db.session.commit() # 提交多对多p = models.Product(pid=‘1’,pname=‘hello’) # 生成或获取商品对象o = models.Order(oid=‘1’, ) # 生成订单对象p.order = [o] # 订单表与商品表关联models.db.session.add(p) # 添加models.db.session.commit() # 提交查一对一models.Product.query.get(pid) # 根据主键返回一个对象,必须查询主键models.Product.query.all() # 返回全部对象models.Product.query.filter_by(pid=pid).first() # 返回第一个对象,检索任何值均可models.Product.query.filter_by(cid=‘1’).limit(4).all() # 限制返回对象一对多# 根据用户获取地址p = models.user.query.get(uid) # 根据主键返回一个对象,必须查询主键p.addresses # 一对多获取对象# 根据地址获取用户u = models.Address.query.get(1)print(u.user)多对多p = models.Product.query.get(1) # 正向查询print(p.order)o = models.Order.query.get(1) # 逆向查询print(o.product)改一对一good = models.Product.query.filter_by(pid=pid).first() # 获取good.pflag = 6 # 修改models.db.session.commit() # 提交一对多u = models.User.query.get(1) for i in u.addresses: i.name = ‘Gage’models.db.session.commit()多对多o = models.Order.query.get(1) for i in o.product: i.pname = ‘Gage’models.db.session.commit()删一对一add = models.Address.query.filter_by(aid=aid).first() # 获取models.db.session.delete(add) # 添加models.db.session.commit() # 提交一对多# 根据用户删除地址# cascade=‘all’ 添加此属性会级联删除# 用户一个用户对应多个地址,因此需要循环遍历,实际开发需使用filter_by 或 filter进行过滤特定删除u = models.User.query.get(1) for i in u.addresses: print(i.aid)models.db.session.delete(i)models.db.session.commit()# 根据地址删除用户a = models.Address.query.get(1)models.db.session.delete(a.user)models.db.session.commit()多对多# 实际开发也是如此,即只删除依赖# 根据商品删除所依赖的订单p = models.Product.query.get(1)o = models.Order.query.get(1)p.order.remove(o)models.db.session.commit()# 根据订单删除所关联的商品p = models.Product.query.get(1)o = models.Order.query.get(1)o.product.remove(p)models.db.session.commit() ...

January 15, 2019 · 2 min · jiezi