【django基础之ORM,增删改查】

一、定义

1.什么是ORM?

ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。

 

2.ORM的优缺点

优点:摆脱复杂的SQL操作,适应快速开发;让数据结构变得简洁;数据库迁移成本更低(如从mysql->oracle)

缺点:性能较差、不适用于大型应用;复杂的SQL操作还需通过SQL语句实现

 

映射关系:

     表名  <-------> 类名

       字段  <-------> 属性

    表记录 <------->类实例对象

二、创建表(建立模型)

实例:我们来假定下面这些概念,字段和关系

作者模型:一个作者有姓名和年龄。

作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)

出版商模型:出版商有名称,所在城市以及email。

书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。

 

 models.py

from django.db import models

# Create your models here.

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)


    read_num=models.IntegerField(default=0)
    commnet_num=models.IntegerField(default=0)
    poll_num=models.IntegerField(default=0)

    # publish:与当前书籍对象关联的的出版社对象,与Publish建立一对多的关系,外键字段建立在多的一方
    publish=models.ForeignKey(to="Publish",to_field="id")

    # authors: 与当前书籍关联的所有作者的集合
    #  与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表

    authors=models.ManyToManyField(to="Author")
    def __str__(self):
        return self.title
class Author(models.Model):
    name=models.CharField(max_length=32)
    age=models.IntegerField()

class AuthorDetail(models.Model):
    tel=models.CharField(max_length=32)
    email=models.CharField(max_length=32)
    
    #与Author建立一对一的关系
    author=models.OneToOneField("Author")



class Publish(models.Model):
    name=models.CharField(max_length=32)
    email=models.CharField(max_length=32)

    def __str__(self):
        return self.name




# #收到创建第三张表
# class Author2Book(models.Model):
#     author=models.ForeignKey(to="Author")
#     book=models.ForeignKey(to="Book")

 

 

语法


#与Author建立一对一的关系 author=models.OneToOneField("Author")
    # 与Publish建立一对多的关系,
    #publish:与当前书籍对象关联的的出版社对象,外键字段建立在多的一方
    publish=models.ForeignKey(to="Publish",to_field="id")

    # authors: 与当前书籍关联的所有作者的集合
    #  与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表

    authors=models.ManyToManyField(to="Author")

 

通过logging可以查看翻译成的sql语句

settings.py

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}  

 

注意事项:

1、 表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称  

2、id 字段是自动添加的

3、对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名

4、这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。

5、定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。

6、外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。

 

字段选项

每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的:

(1)null

如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.

(1)blank

如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。

(2)default

字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。

(3)primary_key

如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。

(4)unique

如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的

(5)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。

这是一个关于 choices 列表的例子:

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)
每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或 ModelChoiceField 中用作显示的内容。 在一个给定的 model 类的实例中,想得到某个 choices 字段的显示值,就调用 get_FOO_display 方法(这里的 FOO 就是 choices 字段的名称 )。例如:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)


>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
View Code

 

二、添加表记录

1.普通字段(单表操作)

方式1

publish_obj=Publish(name="人民出版社",city="北京",email="renMin@163.com")
publish_obj.save() # 将数据保存到数据库

方式2

返回值publish_obj是添加的记录对象
publish_obj=Publish.objects.create(name="人民出版社",city="北京",email="renMin@163.com")<br><br>方式3<br>表.objects.create(**request.POST.dict())

 

2.外键字段

表关系为一对一:在任何一张表中加入一个关联字段,但关联字段必须唯一约束

表关系为一对多:在多的一张表中加关联字段

 

方式1:
   publish_obj=Publish.objects.get(nid=1)
   Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish=publish_obj)
 
方式2:
   Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish_id=1)  

 

3.多对多字段

创建第三张表,主键,两个关联字段。

 

 book_obj=Book.objects.create(title="追风筝的人",publishDate="2012-11-12",price=69,pageNum=314,publish_id=1)

 
author_yuan=Author.objects.create(name="yuan",age=23,authorDetail_id=1)
author_egon=Author.objects.create(name="egon",age=32,authorDetail_id=2)
 
book_obj.authors.add(author_egon,author_yuan)
# 将某个特定的 model 对象添加到被关联对象集合中。 ======= book_obj.authors.add(*[author_egon,author_yuan])

#添加筛选条件的关联,与年龄大于20的关联
author_list=Author.objects.filter(age_gt=20)
bool_obj.authors.add(*author_list)




book_obj.authors.create()
#创建并保存一个新对象,然后将这个对象加被关联对象的集合中,然后返回这个新对象。

 

解除关系:

book_obj.authors.remove()     # 将某个特定的对象从被关联对象集合中去除。    ======   book_obj.authors.remove(*[])
book_obj.authors.clear()       #清空被关联对象集合。

 

例如

def add_book(request):
    if request.method=="POST":
        title=request.POST.get("title")
        price=request.POST.get("price")
        pub_date=request.POST.get("pub_date")
        publish_id=request.POST.get("publish_id")

        #  一对多的添加方式
        # 方式1:
        #book_obj=Book.objects.create(title=title,price=price,publishDate=pub_date,publish_id=publish_id)
        # 方式2:
        # publish_obj=Publish.objects.filter(id=2).first()
        # book_obj=Book.objects.create(title=title, price=price, publishDate=pub_date,publish=publish_obj)

        #  多对多关系的创建
        book_obj = Book.objects.create(title=title, price=price, publishDate=pub_date, publish_id=publish_id)

        print("==",book_obj.authors.all()) # <QuerySet []>

        # add 方法 绑定多对多的关系
        #jing = Author.objects.filter(name="景丽洋").first()
        #alex = Author.objects.filter(name="alex").first()
        #book_obj.authors.add(jing,alex)
        #book_obj.authors.add(*[jing,alex])
        #author_list=Author.objects.filter(age__gt=20)
        #book_obj.authors.add(*author_list)
        # remove 方法 绑定多对多的关系
        book_obj=Book.objects.get(nid=9)
        #alex = Author.objects.filter(name="alex").first()
        #book_obj.authors.remove(alex)
        book_obj.authors.clear()

        #print("==", book_obj.authors.all())

        # 失败的原因:找不到第三张关系表的名字
        # book_authors.objects.create(book_id=book_obj.id,author_id=jing.id)
        # book_authors.objects.create(book_id=book_obj.id,author_id=alex.id)





        return redirect("/index/")

    publish_list=Publish.objects.all()
    return render(request,"add_book.html",locals())
View Code

 

三、修改表记录

 

 Book.objects.filter().update(price=50,.....)

 

四、删除表记录

删除方法就是 delete()。它运行时立即删除对象而不返回任何值。例如:

Book.objects.filter().delete() # 默认级联删除

你也可以一次性删除多个对象。每个 QuerySet 都有一个 delete() 方法,它一次性删除 QuerySet 中所有的对象。

例如,下面的代码将删除 pub_date 是2005年的 Entry 对象:

Entry.objects.filter(pub_date__year=2005).delete()

 

五、查询表记录

1.查询相关API

<1> all():                 查询所有结果,返回的是queryset,集合
 
<2> filter(**kwargs):      它包含了与所给筛选条件相匹配的对象,返回的是queryset,集合
 
<3> get(**kwargs):         返回与所给筛选条件相匹配的对象,返回结果有且只有一个,
                           如果符合筛选条件的对象超过一个或者没有都会抛出错误。返回的是对象
 
<5> exclude(**kwargs):     它包含了与所给筛选条件不匹配的对象
 
<4> values(*field):        返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列
                           model的实例化对象,而是一个可迭代的字典序列
 
<9> values_list(*field):   它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
 
<6> order_by(*field):      对查询结果排序
 
<7> reverse():             对查询结果反向排序
 
<8> distinct():            从返回结果中剔除重复纪录
 
<10> count():              返回数据库中匹配查询(QuerySet)的对象数量。
 
<11> first():              返回第一条记录
 
<12> last():               返回最后一条记录
 
<13> exists():             如果QuerySet包含数据,就返回True,否则返回False

 

2.双下划线之单表查询

models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值
 
models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
 
models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
 
models.Tb1.objects.filter(id__range=[1, 2])      # 范围bettwen and
 
startswith,istartswith, endswith, iendswith 

 

 3.基于对象的跨表操作(子查询)

正向查询:通过关联字段所在的表查询另外一张关联表
反向查询:通过一张关联表查询关联字段所在的表

 

  • 一对多

正向查询,按字段:publish
反向查询,按表名小写_set

 

    #查询java的出版社的邮箱(正向查询:按字段book_obj.publish)
    book_obj=Book.objects.filter(title="java").first()
    print(book_obj.publish.email)
    
  #查询人民出版社出版过的所有的书籍名称(反向查询:按表名小写_set publish_obj.book_set) publish_obj
=Publish.objects.filter(name="人民出版社").first() print("---",publish_obj.book_set.all())

 

  • 多对多

正向查询,按字段:authors
反向查询,按表名小写_set

 

    #查询php这本书籍的所有作者的名字以及年龄(正向查询:按字段book_obj.authors)
    book_obj=Book.objects.filter(title="php").first()
    print(book_obj.authors.all())
    for obj in book_obj.authors.all():
        print(obj.name,obj.age)

    #查询alex出版社过的所有书籍的名称和价格(反向查询,按表名小写_set)
    alex=Author.objects.filter(name="alex").first()
    print(alex.book_set.all())
    for obj in alex.book_set.all():
        print(obj.title,obj.price)

 

  • 一对一

正向查询,按字段:author
反向查询,按表名小写

    # 一对一查询
    查询tel=789的作者的名字  正向查询按字段
    ad=AuthorDetail.objects.filter(tel="789").first()
    print(ad.author.name)
    
    # 查询alex的手机号是多少
    alex=Author.objects.filter(name='alex').first()
    print(alex.authordetail.tel) # 789

 

4.基于QuerySet的跨表查询(join查询)

正向查询,按字段:publish
反向查询,按表名小写

 

  • 一对多

例1:
            查询java这本书的出版社的邮箱(正向查询)
            
            ret=Book.objects.filter(nid__gt=6).values("publish__name")
            
            sql:
                SELECT "app01_publish"."name"   
                FROM "app01_book" INNER JOIN "app01_publish" 
                ON ("app01_book"."publish_id" = "app01_publish"."id") 
                WHERE "app01_book"."nid" > 6 LIMIT 21;
                
            values实现机制:
                 对调用的QuerySet集合对象里面的每一个对象循环遍历,取出每一个对象的显示字段的值,组成新的字典,放在一个新的QuerySet中,返回
            
            例2:
            # 查询人民出版社出版过的所有的书籍名称(反向查询)
            ret=Publish.objects.filter(name="人民出版社").values("book__title")
            print(ret)        
            #<QuerySet [{'book__title': 'golang'}, {'book__title': 'linux2'}, {'book__title': 'qq'}, {'book__title': 'www'}]>

 

  • 多对多

 

    #查询php这本书籍的所有作者的名字以及年龄
    ret=Book.objects.filter(title="php").values("authors__name","authors__age")
    print(ret)
    #查询alex出版社过的所有书籍的名称和价格
    ret=Author.objects.filter(name="alex").values("book__title","book__price")
    print(ret)

 

  • 一对一

    #查询tel=789的作者的名字
    ret=AuthorDetail.objects.filter(tel="789").values("author__name")
    # 查询alex的手机号是多少
    ret=Author.objects.filter(name="alex").values("authordetail__tel")

 

扩展

扩展:
        #查询人民出版社出版过的所有的书籍名称(两种查询思路,基表不同)
        
        ret=Publish.objects.filter(name="人民出版社").values("book__title")
        ret=Book.objects.filter(publish__name="人民出版社").values("title")
    
        # 手机号以151开头的作者出版过的所有书籍名称以及出版社名称

        ret=Book.objects.filter(authors__authordetail__tel__startswith="7").values("title","publish__name")
        print(ret)

           SELECT "app01_book"."title", "app01_publish"."name" FROM "app01_book" 
           INNER JOIN "app01_book_authors" ON ("app01_book"."nid" = "app01_book_authors"."book_id") 
           INNER JOIN "app01_author" ON ("app01_book_authors"."author_id" = "app01_author"."id") 
           INNER JOIN "app01_authordetail" ON ("app01_author"."id" = "app01_authordetail"."author_id") 
           INNER JOIN "app01_publish" ON ("app01_book"."publish_id" = "app01_publish"."id") 
           WHERE 
           "app01_authordetail"."tel" LIKE '7%' ESCAPE '\' LIMIT 21; args=('7%',)

 

5.聚合查询与分组查询

聚合函数:Sum  Count Avg Min Max
    
    单表:
    emp:
          id   name  salary    dep    
           1   张三    2000   销售部
           2   李四    5000   IT部
           3   王五    6000   销售部
           4   赵六    5000   人事部
           5   主七    2000   人事部
        
        select Count(*),dep from emp group by dep           
        select * from emp group by id,name,salary,dep           
     

    跨表:              
    emp:                                      dep:
          id   name  salary  dep_id                     id            name
           1   张三    2000     1                       1         销售部
           2   李四    5000     2                       2         IT部
           3   王五    6000     1                       3         人事部
           4   赵六    5000     3                
           5   主七    2000     2                           
        
        

dep---emp

id        dep.id    dep.name  emp.id emp.name emp.salary emp.dep_id
1        1         销售部       1   张三    2000     1  
2        1         销售部       3   王五    6000     1  
3        2         IT部         2   李四    5000     2 
4        2         IT部         5   主七    2000     2  
5        3         人事部       4   赵六    5000     3 
        
        select Count(*) from emp
        select * from dep group by id            
    
    
    
      SELECT "app01_publish"."name", COUNT("app01_book"."title") AS "c"   
      FROM "app01_publish" LEFT OUTER JOIN "app01_book" ON ("app01_publish"."id" = "app01_book"."publish_id") 
      GROUP BY "app01_publish"."id", "app01_publish"."name", "app01_publish"."email" LIMIT 21; args=()
辅助理解

 

  • 聚合:aggregate(*args, **kwargs)

# 计算所有图书的平均价格
    >>> from django.db.models import Avg
    >>> Book.objects.all().aggregate(Avg('price'))
    {'price__avg': 34.35}

aggregate()QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。

>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

 

    # 聚合函数  aggregate
    from django.db.models import Sum,Count,Max,Min,Avg
    # ret=Book.objects.all().aggregate(Sum("price"))
    # print(ret)

 

  • 分组:annotate()

为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。

统计每一本书的作者个数

bookList=Book.objects.annotate(authorsNum=Count('authors'))
for book_obj in bookList:
    print(book_obj.title,book_obj.authorsNum)
SELECT 
"app01_book"."nid", 
"app01_book"."title", 
"app01_book"."publishDate", 
"app01_book"."price", 
"app01_book"."pageNum", 
"app01_book"."publish_id", 
COUNT("app01_book_authors"."author_id") AS "authorsNum" 
FROM "app01_book" LEFT OUTER JOIN "app01_book_authors" 
ON ("app01_book"."nid" = "app01_book_authors"."book_id") 
GROUP BY 
"app01_book"."nid", 
"app01_book"."title", 
"app01_book"."publishDate", 
"app01_book"."price", 
"app01_book"."pageNum", 
"app01_book"."publish_id"

sql
sql解析

 

Book.objects.annotate(authorsNum=Count('authors'))
拆分解析:
Book.objects等同于Book.objects.all(),翻译成的sql类似于: select id,name,..  from Book
这样得到的对象一定是每一本书对象,有n本书籍记录,就分n个组,不会有重复对象,每一组再由annotate分组统计。

(2) 如果想对所查询对象的关联对象进行聚合:

统计每一个出版社的最便宜的书

publishList=Publish.objects.annotate(MinPrice=Min("book__price"))
 
for publish_obj in publishList:
    print(publish_obj.name,publish_obj.MinPrice)

annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:

queryResult= Publish.objects
            .annotate(MinPrice=Min("book__price"))
            .values_list("name","MinPrice")
print(queryResult)

 

统计每一本以py开头的书籍的作者个数:

 queryResult=Book.objects
           .filter(title__startswith="Py")
           .annotate(num_authors=Count('authors'))

统计不止一个作者的图书
queryResult=Book.objects
          .annotate(num_authors=Count('authors'))
          .filter(num_authors__gt=1)


        # 查询每一个出版社出版社出版的书籍个数
        # ret=Publish.objects.all().annotate(c=Count("book__title")).values("name","c")
        # print(ret) # <QuerySet [<Publish: 人民出版社>, <Publish: 机械出版社>, <Publish: 北京出版社>]>

        # 查询每一本书的作者个数
        #ret=Book.objects.all().annotate(author_num=Count("authors")).values("author_num","title")
        # ret=Book.objects.all().annotate(author_num=Count("authors")).filter(author_num__gt=0)
        # print(ret)

 

6.F查询和Q查询

  • F查询

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

# 查询评论数大于收藏数的书籍
 
   from django.db.models import F
   Book.objects.filter(commnetNum__lt=F('keepNum'))

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。

# 查询评论数大于收藏数2倍的书籍
    Book.objects.filter(commnetNum__lt=F('keepNum')*2)

修改操作也可以使用F函数,比如将每一本书的价格提高30元:

    
Book.objects.all().update(price=F("price")+30) 

 

  • Q查询

filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用对象

from django.db.models import Q
Q(title__startswith='Py')

Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))

等同于下面的SQL WHERE 子句:WHERE name ="yuan" OR name ="egon"

你可以组合& 和|  操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:

bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(publishDate__year=2017)).values_list("title")

查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:

    bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),
                                  title__icontains="python"
                                 )

 

    # 评论数大于点赞数的书籍
    from django.db.models import F ,Q
    # ret=Book.objects.filter(poll_num__gt=F("commnet_num")*2)
    # print(ret)
    #Book.objects.all().update(price=F("price")+100)
    # 查询 2018-01-09出版的或者价格大于150的书籍
    ret=Book.objects.filter(Q(publishDate="2018-01-17")&~Q(price__gt=300))
    print(ret)
    return HttpResponse("OK")

 

posted @ 2018-01-25 22:08  小火星_Hirsi  阅读(351)  评论(0编辑  收藏  举报