zoukankan      html  css  js  c++  java
  • 06 django模型之多表操作

    一、创建表结构

    作者与作者详细信息是一对一的关系(onetoone)

    出版社与书的关系是一对多(onetomany)

    书与作者的关系是多对多(manytomany)

    建立的表结构如下:

    from django.db import models
    
    # Create your models here.
    
    
    class Author(models.Model): #比较常用的信息放到这个表里面
        nid = models.AutoField(primary_key=True)
        name=models.CharField( max_length=32)
        age=models.IntegerField()
    
        # 与AuthorDetail建立一对一的关系,一对一的这个关系字段写在两个表的任意一个表里面都可以
        authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE) #就是foreignkey+unique,只不过不需要我们自己来写参数了,并且orm会自动帮你给这个字段名字拼上一个_id,数据库中字段名称为authorDetail_id
    
    class AuthorDetail(models.Model):#不常用的放到这个表里面
    
        nid = models.AutoField(primary_key=True)
        birthday=models.DateField()
        telephone=models.BigIntegerField()
        addr=models.CharField( max_length=64)
    
    class Publish(models.Model):
        nid = models.AutoField(primary_key=True)
        name=models.CharField( max_length=32)
        city=models.CharField( max_length=32)
        email=models.EmailField()
    
    #多对多的表关系,我们学mysql的时候是怎么建立的,是不是手动创建一个第三张表,然后写上两个字段,每个字段外键关联到另外两张多对多关系的表,orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以,以后的学习我们暂时用orm自动创建的第三张表,因为手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用#如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。
    class Book(models.Model):
    
        nid = models.AutoField(primary_key=True)
        title = models.CharField( max_length=32)
        publishDate=models.DateField()
        price=models.DecimalField(max_digits=5,decimal_places=2)
    
        # 与Publish建立一对多的关系,外键字段建立在多的一方,字段publish如果是外键字段,那么它自动是int类型
        publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) #foreignkey里面可以加很多的参数,都是需要咱们学习的,慢慢来,to指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete级联删除   字段名称不需要写成publish_id,orm在翻译foreignkey的时候会自动给你这个字段拼上一个_id,这个字段名称在数据库里面就自动变成了publish_id
        # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,并且注意一点,你查看book表的时候,你看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,所以只能说这个book类里面有authors这个字段属性
        authors=models.ManyToManyField(to='Author',) #注意不管是一对多还是多对多,写to这个参数的时候,最后后面的值是个字符串,不然你就需要将你要关联的那个表放到这个表的上面

    1、创建多对多关系第三张表方式

    方式一:自己创建第三张表

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
    
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
    
    
    # 自己创建第三张表,分别通过外键关联书和作者
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")
    
        class Meta:
            unique_together = ("author", "book")

    方式二:通过ManyToManyField创建表

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
    
    # 通过ORM自带的ManyToManyField自动创建第三张表
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to="Book", related_name="authors")  #自动生成的第三张表我们是没有办法添加其他字段的

    方式三:设置ManyToManyField并指定自己创建第三张表(中介模式)

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
    
    # 自己创建第三张表,并通过ManyToManyField指定关联
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
        # through_fields接受一个2元组('field1''field2'):
        # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。
    
    
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")
        #可以扩展其他的字段了
        class Meta:
            unique_together = ("author", "book")

    注意:

      当我们需要在第三张关系表中存储额外的字段时,就要使用第三种方式,第三种方式还是可以使用多对多关联关系操作的接口(all、add、clear等等)

      当我们使用第一种方式创建多对多关联关系时,就无法使用orm提供的set、add、remove、clear方法来管理多对多的关系了。

    2、参数

    2.1、创建一对一关系字段的一些参数

    to
        设置要关联的表。
    
    to_field
        设置要关联的字段。
        
    on_delete
        同ForeignKey字段。

    2.2、创建一对多关系字段的一些参数

    to
        设置要关联的表
    
    to_field
        设置要关联的表的字段
    
    related_name
        反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
    related_query_name
        反向查询操作时,使用的连接前缀,用于替换表名。
    
    on_delete
        当删除关联表中的数据时,当前表与其关联的行的行为。

    2.3、创建多对多关系字段的一些参数

    多对多的参数:
        to
            设置要关联的表
    
        related_name
            同ForeignKey字段。
    
        related_query_name
            同ForeignKey字段。
        through
            在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
    
            但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过        
        through来指定第三张表的表名。
    
        through_fields
            设置关联的字段。
    
        db_table
            默认创建第三张表时,数据库中表的名称。 

    2.4、创建表时的一些元信息设置

    元信息
        ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")
        class Meta:
            unique_together = ("author", "book")
    
    db_table
        ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'
    
    index_together
        联合索引。
    
    unique_together
        联合唯一索引。
    
    ordering
        指定默认按什么字段排序。
        ordering = ['pub_date',]
        只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)

    2.5、关于on_delete

    on_delete
    当删除关联表中的数据时,当前表与其关联的行的行为。
    
    models.CASCADE
    删除关联数据,与之关联也删除
    
    
    models.DO_NOTHING
    删除关联数据,引发错误IntegrityError
    
    
    models.PROTECT
    删除关联数据,引发错误ProtectedError
    
    
    models.SET_NULL
    删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
    
    
    models.SET_DEFAULT
    删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
    
    
    models.SET
    
    删除关联数据,
    a. 与之关联的值设置为指定值,设置:models.SET(值)
    b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

    2.6、ForeignKey的db_contraint

    ForeignKey的db_contraint参数
    
    关系和约束大家要搞清楚,我不加外键能不能表示两个表之间的关系啊,当然可以
    
    但是我们就不能使用ORM外键相关的方法了,所以我们单纯的将外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的。
    
    #db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果将来公司让你建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False
        customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)

    二、添加表记录

    1、一对多

    方式1:
        publish_obj=Publish.objects.get(nid=1) #拿到nid为1的出版社对象
        book_obj=Book.objects.create(title="西游记",publishDate="2012-12-12",price=100,publish=publish_obj)
    方式2:
        book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1)  
    #直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,上面是publish=xxx。

    2、多对多

    方式1:
        # 当前生成的书籍对象
        book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1)
        # 为书籍绑定的做作者对象
        ee=Author.objects.filter(name="ee").first()
        rr=Author.objects.filter(name="alex").first()
        book_obj.authors.add(ee,rr)
    #  将某些特定的 model 对象添加到被关联对象集合中。  book_obj.authors.add(*[])
    #book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称。
    #其实orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去
    
    方式2:
        book_obj.authors.add(34)
        book_obj.authors.add(*[34]) #这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值

    多对多关系其他常用API:

    book_obj.authors.remove()      # 将某个特定的对象从被关联对象集合中去除。    
    # ======   book_obj.authors.remove(*[12]),将多对多的关系数据删除
    book_obj.authors.clear()       #清空被关联对象集合
    book_obj.authors.set()         #先清空再设置   ===== 

    删除示例:

    book_obj = models.Book.objects.filter(nid=4)[0]
    # book_obj.authors.remove(2) #将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除
    # book_obj.authors.clear()
    # book_obj.authors.set('2') #先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录  3---2,比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以我们就可以先清空,然后再重新绑定关系数据,注意这里写的是字符串,数字类型不可以
    book_obj.authors.set(['1',]) #这么写也可以,但是注意列表中的元素是字符串,列表前面没有*,之前我测试有*,感觉是版本的问题,没事,能够用哪个用哪个
    更新:
    book_obj = models.Book.objects.get(id=1) #获取一个书籍对象
    data = {'title':'xxx','price':100} #这个书籍对象更新后的数据
    models.Book.objects.filter(id=n).update(**data) #将新数据更新到原来的记录中
    book_obj.authors.set(author_list) #将数据和作者的多对多关系加上
    
    删除:
    models.Book.objects.filter(id=1).delete()

    二、基于对象的跨表查询

    1、一对多查询(Publish与Book)

    关联字段在哪个类里从那张表开始查就是正向查询,反之反向查询

    正向查询(字段publish)

    # 查询主键为1的书籍的出版社所在的城市
    book_obj=Book.objects.filter(pk=1).first()
    # book_obj.publish 是主键为1的书籍对象关联的出版社对象,book对象.外键字段名称
    print(book_obj.publish.city)  

    反向查询(表名:book_set):加上_set是因为反向查询的时候,你查询出来的可能是多条记录的集合

    publish=Publish.objects.get(name="苹果出版社")
    #publish.book_set.all() : 与苹果出版社关联的所有书籍对象集合,写法:小写的表名_set.all(),得到queryset类型数据
    book_list=publish.book_set.all()    
    for book_obj in book_list:
           print(book_obj.title)

    2、一对一查询(Author和AuthorDetail)

    正向查询(authorDetail):

    qq=Author.objects.filter(name="qq").first()
    print(qq.authorDetail.telephone) qq.authorDeail就拿到了这个对象,因为一对一找到的就是一条记录,注意写法:作者对象.字段名,就拿到了那个关联对象

    反向查询(按表名:author)不需要_set,因为一对一正向反向都是一条记录

    # 查询所有住址在  斤斤计较  的作者的姓名
     
    authorDet=AuthorDetail.objects.filter(addr="斤斤计较")[0]
    authorDet.author.name

    3、多对多查询(Author与Book)

    正向查询(字段:authors):

    # 西游记所有作者的名字以及手机号
     
    book_obj=Book.objects.filter(title="西游记").first()
    authors=book_obj.authors.all()
    for author_obj in authors:
         print(author_obj.name,author_obj.authorDetail.telephone

    反向查询(表名:book_set):

    # 查询qq出过的所有书籍的名字
     
        author_obj=Author.objects.get(name="qq")
        book_list=author_obj.book_set.all()        #与qq作者相关的所有书籍
        for book_obj in book_list:
            print(book_obj.title)

    可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改

    publish = ForeignKey(Book, related_name='bookList')
    # 查询 北京出版社出版过的所有书籍
     
    publish=Publish.objects.get(name="北京出版社")
    book_list=publish.bookList.all()  # 与北京出版社关联的所有书籍对象集合

    三、基于双下划线的跨表查询(基于join实现的)

    # 正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表,
    # 一对一、一对多、多对多都是一个写法,注意,我们写orm查询的时候,哪个表在前哪个表在后都没问题,因为走的是join连表操作。

    1、一对多查询

    # 练习:  查询北京出版社出版过的所有书籍的名字与价格(一对多) 
    
        # 正向查询 按字段:publish
    
        queryResult=Book.objects
                .filter(publish__name="北京出版社")  #通过__告诉orm将book表和publish表进行join,然后找到所有记录中publish.name='北京出版社'的记录(注意publish是属性名称),然后select book.title,book.price的字段值
                .values_list("title","price") #values或者values_list
    
        # 反向查询 按表名:book
    
        queryResult=Publish.objects
                  .filter(name="北京出版社")
                  .values_list("book__title","book__price")

    2、多对多查询

    # 练习: 查询qq出过的所有书籍的名字(多对多)
    
        # 正向查询 按字段:authors:
        queryResult=Book.objects
                .filter(authors__name="qq")
                .values_list("title")
    
        # 反向查询 按表名:book
        queryResult=Author.objects
                  .filter(name="qq")
                  .values_list("book__title","book__price")

    3、一对一查询

    # 查询aa的手机号
        
        # 正向查询
        ret=Author.objects.filter(name="aa").values("authordetail__telephone")
    
        # 反向查询
        ret=AuthorDetail.objects.filter(author__name="aa").values("telephone")

    4、连续跨表练习

    # 练习: 查询北京出版社出版过的所有书籍的名字以及作者的姓名
    
    
        # 正向查询
        queryResult=Book.objects
                .filter(publish__name="北京出版社")
                .values_list("title","authors__name")
        # 反向查询
        queryResult=Publish.objects
                  .filter(name="北京出版社")
                  .values_list("book__title","book__authors__age","book__authors__name")
    
    
    # 练习: 手机号以178开头的作者出版过的所有书籍名称以及出版社名称
    
    
        # 方式1:
        queryResult=Book.objects
                .filter(authors__authorDetail__telephone__regex="178")
                .values_list("title","publish__name")
        # 方式2:    
        ret=Author.objects
                  .filter(authordetail__telephone__startswith="178")
                  .values("book__title","book__publish__name")

    5、related_name

    反向查询时,如果定义了related_name ,则用related_name替换 表名,例如:

    publish = ForeignKey(Blog, related_name='bookList')
    # 练习: 查询北京出版社出版过的所有书籍的名字与价格(一对多)
    
    # 反向查询 不再按表名:book,而是related_name:bookList
    
    
        queryResult=Publish.objects
                  .filter(name="北京出版社")
                  .values_list("bookList__title","bookList__price")

    四、聚合查询、分组查询、F查询和Q查询

    1、聚合查询

    # 计算所有图书的平均价格
    from django.db.models import Avg,Max,Min,Count,Sum
    Book.objects.all().aggregate(Avg('price')) #或者给它起名字:aggretate(a=Avg('price'))
    {'price__avg': 34.35}

    如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:

    from django.db.models import Avg,Max,Min,Count,Sum
    Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))  #count('id'),count(1)也可以统计个数,Book.objects.all().aggregete和Book.objects.aggregate(),都可以
    
    {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

    2、分组查询

    # 统计一下每个出版社出版书的平均价格
    原生sql
    根据出版社id分组
    select publishs_id,avg(price) from app01_book group by publishs_id;
    根据出版社名分组
    select avg(app01_book.price) from app01_book inner join app01_publish on app01_book.publishs_id = app01_publish.id group by app01_publish.name;
    
    orm
    ret = models.Book.objects.values('publishs_id').annotate(a=Avg('price'))
    print(ret)
    ret = models.Publish.objects.annotate(a=Avg('book__price'))
    print(ret.values('a','name'))

    3、F查询

    在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?我们在book表里面加上两个字段:评论数:comment,点赞数:dianzan

    Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值

    from django.db.models import F
    # 点赞数大于评论数的
    # ret = models.Book.objects.filter(dianzan__gt=F('comment'))
    # print(ret)
    # 所有书籍上调10块
    models.Book.objects.all().update(price=F('price')+10) #支持四则运算

    4、Q查询

    filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
    from django.db.models import Q
    Q(title__startswith='Py')
    Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象
    ret = models.Book.objects.filter(Q(comment__gt=30)|Q(dianzan__gt=50))
    ret = models.Book.objects.filter(Q(comment__gt=30)&Q(dianzan__gt=50))
    等同于# ret = models.Book.objects.filter(comment__gt=30,dianzan__gt=50)
    
    ret = models.Book.objects.filter(Q(comment__gt=30) | Q(dianzan__gt=50),publishDate__year='2018')
    # 注意没有Q包裹的条件,写在Q包裹的条件后面.
    # 多层嵌套
    # ret = models.Book.objects.filter(Q(Q(comment__gt=30) | Q(dianzan__gt=50))&Q(xx=11),publishDate__year='2018')
    
    # 条件取反
    # 取评论数小于等于30 的,或者点赞数大于50的
    # ret = models.Book.objects.filter(~Q(comment__gt=30)|Q(dianzan__gt=50))
    # print(ret)
    

      

    五、ORM执行原生sql语句

    执行原生sql

    Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。

    raw()管理器方法用于原始的SQL查询,并返回模型的实例:

    注意:raw()语法查询必须包含主键

    这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例。

    例:

    class Person(models.Model):
        first_name = models.CharField(...)
        last_name = models.CharField(...)
        birth_date = models.DateField(...)

    可以像下面这样执行原生SQL语句

    for p in Person.objects.raw('SELECT * FROM myapp_person'):
        print(p)

    raw()方法自动将查询字段映射到模型字段。还可以通过translations参数指定一个把查询的字段名和ORM对象实例的字段名互相对应的字典

    d = {'tname': 'haha'}
        ret = models.Student.objects.raw('select * from app02_teacher', translations=d)
        for i in ret:
            print(i.id, i.sname, i.haha)

    原生SQL还可以使用参数,注意不要自己使用字符串格式化拼接SQL语句,防止SQL注入!

    d = {'tname': 'haha'}
        ret = models.Student.objects.raw('select * from app02_teacher where id > %s', translations=d, params=[1,])
        for i in ret:
            print(i.id, i.sname, i.haha)

    执行自定义sql

    有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。

    我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。

    from django.db import connection, connections
    cursor = connection.cursor()  # cursor = connections['default'].cursor()
    cursor.execute("""SELECT * from auth_user where id = %s""", [1])
    ret = cursor.fetchone()
  • 相关阅读:
    mysql进阶
    浅谈数据库查询操作时的顺序
    Problem C Emergency Evacuation 一道思维题
    c++随机生成树
    洛谷 P4408 [NOI2003]逃学的小孩
    UVA11300 Spreading the Wealth
    洛谷 P3574 [POI2014]FAR-FarmCraft
    洛谷 P2882 [USACO07MAR]Face The Right Way G
    JSOI BZOJ4472 salesman
    CF 1912 A NEKO's Maze Game
  • 原文地址:https://www.cnblogs.com/whc6/p/14502710.html
Copyright © 2011-2022 走看看