python-django-03-django-ORM入门

本文详细介绍了ORM的概念,如何配置数据库连接、创建模型、迁移数据,以及在Django中使用ORM进行增删改查操作。重点讲解了字段类型、关系字段、查询API和多表操作,以及高级查询技巧如聚合函数和分组查询。
摘要由CSDN通过智能技术生成

ORM简介

ORM即Object Relational Mapping,全称对象关系映射

        当我们需要对数据库进行操作时,势必需要通过连接数据、调用sql语句、执行sql语句等操作,ORM将数据库中的表,字段,行与我们面向对象编程的类及其方法,属性等一一对应,即将该部分操作封装起来,程序猿不需懂得sql语句即可完成对数据库的操作。

ORM优点:
    易用,学习曲线短 
    和Django紧密集合,用Django时使用约定俗成的方法去操作数据库 

缺点:
    不好处理复杂的查询,强制开发者回到原生SQL 
    紧密和Django集成,使得在Django环境外很难使用 

一、ORM数据库配置

1.1、数据库连接

  1. 配置数据库连接
DATABASES = {
    # sqlite3 默认的,不需要更改
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    # }   
    # mysql 数据库 需要注意的是数据库不会自动创建,需要手动先进行创建数据库,表可以自动创建 
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': "192.168.9.224",
        "USER": "test",
        "PASSWORD": "test",
        "PORT": 3306,
        "NAME":"day71"
    }
}

'''
    'NAME':要连接的数据库,连接前需要创建好
    'USER':连接数据库的用户名
    'PASSWORD':连接数据库的密码
    'HOST':连接主机,默认本机
    'PORT':端口 默认3306
    'ATOMIC_REQUEST': True,

    设置为True统一个http请求对应的所有sql都放在一个事务中执行(要么所有都成功,要么所有都失败)。
    是全局性的配置, 如果要对某个http请求放水(然后自定义事务),可以用non_atomic_requests修饰器 
    'OPTIONS': {
                 "init_command": "SET storage_engine=INNODB",
                }
    设置创建表的存储引擎为MyISAM,INNODB
'''
  1. 配置连接引擎
# 工程中的 __init__.py中添加,  在django项目启动时默认启动pymysql连接数据引擎
    import pymysql
    pymysql.version_info = (1, 4, 13, "final", 0)
    pymysql.install_as_MySQLdb()

# 这里会有一个错误  版本 django3.1.1  python3.7
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.4.0 or newer is required; you have 0.10.1.
# 解决方案 添加:pymysql.version_info = (1, 4, 13, "final", 0)
# 可参考:https://blog.csdn.net/knight_zhou/article/details/108576312
  1. 创建模型

    # models.py  创建模型
    from django.db import models
    
    # Create your models here.
    class User(models.Model):
        id = models.AutoField(primary_key=True)
        user = models.CharField(max_length=30)
        passwd = models.CharField(max_length=20)
    
  2. 数据迁移

>python manage.py makemigrations		# 记录数据库的变化
    Migrations for 'myapps':
      myapps\migrations\0001_initial.p
        - Create model Users

>python manage.py migrate				# 将变化同步到数据库中

# python manage.py makemigrations   这一步只是将记录保存到了migratons文件下,并没有提交到数据库
# python manage.py migrate     将记录在migrations的信息提交到数据库中
# 查看是否有未提交的  python manage.py showimigrate
  1. 简单使用
# app下的views.py
from django.shortcuts import render, HttpResponse
from myapps import models

def login2(request):
    if request.method == "GET":
        return render(request, "login2.html")

    elif request.method == "POST":
        user = request.POST.get("username")
        pwd = request.POST.get("pwd")
        user = models.Users.objects.filter(username=user, password=pwd).first()
		# 如果有数据就返回登陆成功,否则就返回用户密码错误
        return HttpResponse("登陆成功" if user else "用户或密码错误")

# 在添加一条路由
from myapps.views import *

urlpatterns = [
    path('login2/', login2),
]

1.2、示例demo

  1. 创建一个app应用以及生成对应的model
# 4步曲
	1、创建app
    2、创建models
    3、在django中注册app
    4、数据迁移

# twodjango> python manage.py startapp curddemo

# curddemo/models.py
    from django.db import models

    # Create your models here.
    class Curds(models.Model):
        id = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32)
        password = models.CharField(max_length=16)
        address = models.CharField(max_length=128)

# twodjango/settings.py 注册app应用
INSTALLED_APPS = [
    ...
    'curddemo.apps.CurddemoConfig'
]

# 迁移数据 》  注意这里是在控制台下操作
    # twodjango> python manage.py makemigrations
    # twodjango> python manage.py migrate
    
# 基础导入:  curddemo/views.py
    from django.shortcuts import render, redirect
    from curddemo import models

# urls.py
from curddemo import views

urlpatterns = [
    path('c_index/', views.c_index),
    path('c_del/', views.c_del),
    path('c_addr/', views.c_addr),
    path('c_update/', views.c_update),
]
  1. 首页展示
def c_index(request):
    # 产生的是一个questSet列表, 列表中包含N个对象
    curd_list = models.Curds.objects.all()
    return render(request, "curd/index.html", {"curd_list": curd_list})

# templates/curd/index.html
<link rel="stylesheet" href="/static/css/bootstrap.css">

<div class="col-md-6 col-md-offset-3">
    <h5><a href="/c_addr/">添加用户</a></h5>
    <table border="1">
        <tr>
            <th>id</th>
            <th>name</th>
            <th>password</th>
            <th>address</th>
            <td>操作</td>
        </tr>
        {% for curd in curd_list %}
            <tr>
                <td>{{ forloop.counter }}</td>
                <td>{{ curd.name }}</td>
                <td>{{ curd.password }}</td>
                <td>{{ curd.address }}</td>
                <td>
                    <a href="/c_del/?id={{ curd.id }}">删除</a> |
                    <a href="/c_update/?id={{ curd.id }}">编辑</a>
                </td>

            </tr>
        {% endfor %}

    </table>
</div>
  1. 删除用户
def c_del(request):
    id = request.GET.get("id")
    models.Curds.objects.filter(id=id).delete()
    return redirect("/c_index/")

# 对应的html页面  <a href="/c_del/?id={{ curd.id }}">删除</a>
  1. 添加用户
def c_addr(request):
    if request.method == "GET":
        return render(request, "curd/c_addr.html")

    elif request.method == "POST":
        name = request.POST.get("name")
        pwd = request.POST.get("pwd")
        addr = request.POST.get("addr")
        res = models.Curds.objects.create(name=name, password=pwd, address=addr)
        print(res)
        return redirect("/c_index/")

# templates/curd/c_addr.html
    <div class="col-md-7 col-md-offset-3">
        <h3>添加页面</h3>
        <form action="/c_addr/" method="post">
            <p>用户: <input type="text" name="name" class="form-control"></p>
            <p>密码: <input type="text" name="pwd" class="form-control"></p>
            <p>地址: <input type="text" name="addr" class="form-control"></p>
            <p><input type="submit" value="提交"></p>
        </form>
    </div>
  1. 编辑用户
def c_update(request):
    if request.method == "GET":
        id = request.GET.get("id")
        get_info = models.Curds.objects.filter(id=id).first()
        print(get_info)
        return render(request, "curd/c_update.html", {"get_info": get_info})

    elif request.method == "POST":
        id = request.POST.get("id")
        name = request.POST.get("name")
        pwd = request.POST.get("pwd")
        addr = request.POST.get("addr")
        print(request.POST)
        models.Curds.objects.filter(id=id).update(name=name,password=pwd, address=addr)
        return redirect("/c_index/")


# templates/curd/c_update.html
    <div class="col-md-5 col-md-offset-3">
        <h3>修改页面</h3>
        <form action="/c_update/" method="post">
            <p><input type="text" name="id" class="hidden" value="{{ get_info.id }}"></p>
            <p>用户: <input type="text" name="name" class="form-control" value="{{ get_info.name }}"></p>
            <p>密码: <input type="text" name="pwd" class="form-control" value="{{ get_info.password }}"></p>
            <p>地址: <input type="text" name="addr" class="form-control"  value="{{ get_info.address }}"></p>
            <p><input type="submit" value="提交"></p>
        </form>
    </div>
  1. 关键字段说明
```python
1 orm 创建表,新增字段,修改,删除字段,不能创建数据库
	-字段属性phone=models.CharField(max_length=64,null=True)
	-null=True 代表该列可以为空

2 数据的增删改查
	-增(1):User.objects.create(address='')
	-增(2):实例化产生一个user对象,user对象的save方法
	# 用户对象
	-删:User.objects.filter(id=1,name='lqz').first().delete()  
	# queryset对象
	-删:User.objects.filter(id=1,name='lqz').delete()
	-改:User.objects.filter(id=1,name='lqz').update()
	# querySet对象无法直接 .属性,需要是用户对象才能加.属性
	-查:User.objects.all()
	    user=User.objects.filter(name='lqz')
		user.name
```

1.3、ORM日志查看

只需要在settings.py文件中加上这几行, 控制台就能直接查看到orm对应的sql了
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

二、ORM模型

2.1、字段

AutoField(Field)
    - int自增列,必须填入参数 primary_key=True

BigAutoField(AutoField)
    - bigint自增列,必须填入参数 primary_key=True

SmallIntegerField(IntegerField):
    - 小整数 -3276832767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正小整数 032767

IntegerField(Field)
    - 整数列(有符号的) -21474836482147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正整数 02147483647

BigIntegerField(IntegerField):
    - 长整型(有符号的) -92233720368547758089223372036854775807

    自定义无符号整数字段

        class UnsignedIntegerField(models.IntegerField):
            def db_type(self, connection):
                return 'integer UNSIGNED'

        PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
            'AutoField': 'integer AUTO_INCREMENT',
            'BigAutoField': 'bigint AUTO_INCREMENT',
            'BinaryField': 'longblob',
            'BooleanField': 'bool',
            'CharField': 'varchar(%(max_length)s)',
            'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
            'DateField': 'date',
            'DateTimeField': 'datetime',
            'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
            'DurationField': 'bigint',
            'FileField': 'varchar(%(max_length)s)',
            'FilePathField': 'varchar(%(max_length)s)',
            'FloatField': 'double precision',
            'IntegerField': 'integer',
            'BigIntegerField': 'bigint',
            'IPAddressField': 'char(15)',
            'GenericIPAddressField': 'char(39)',
            'NullBooleanField': 'bool',
            'OneToOneField': 'integer',
            'PositiveIntegerField': 'integer UNSIGNED',
            'PositiveSmallIntegerField': 'smallint UNSIGNED',
            'SlugField': 'varchar(%(max_length)s)',
            'SmallIntegerField': 'smallint',
            'TextField': 'longtext',
            'TimeField': 'time',
            'UUIDField': 'char(32)',

    BooleanField(Field)
        - 布尔值类型

    NullBooleanField(Field):
        - 可以为空的布尔值

    CharField(Field)
        - 字符类型
        - 必须提供max_length参数, max_length表示字符长度

    TextField(Field)
        - 文本类型

    EmailField(CharField)- 字符串类型,Django Admin以及ModelForm中提供验证机制

    IPAddressField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

    GenericIPAddressField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
        - 参数:
            protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
            unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"

    URLField(CharField)
        - 字符串类型,Django Admin以及ModelForm中提供验证 URL

    SlugField(CharField)
        - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

    CommaSeparatedIntegerField(CharField)
        - 字符串类型,格式必须为逗号分割的数字

    UUIDField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

    FilePathField(Field)
        - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
        - 参数:
                path,                      文件夹路径
                match=None,                正则匹配
                recursive=False,           递归下面的文件夹
                allow_files=True,          允许文件
                allow_folders=False,       允许文件夹

    FileField(Field)
        - 字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

    ImageField(FileField)
        - 字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
            width_field=None,   上传图片的高度保存的数据库字段名(字符串)
            height_field=None   上传图片的宽度保存的数据库字段名(字符串)

    DateTimeField(DateField)
        - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

    DateField(DateTimeCheckMixin, Field)
        - 日期格式      YYYY-MM-DD

    TimeField(DateTimeCheckMixin, Field)
        - 时间格式      HH:MM[:ss[.uuuuuu]]

    DurationField(Field)
        - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

    FloatField(Field)
        - 浮点型

    DecimalField(Field)
        - 10进制小数
        - 参数:
            max_digits,小数总长度
            decimal_places,小数位长度

    BinaryField(Field)
        - 二进制类型

2.2、参数

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

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

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

(4)primary_key
	如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
	Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True(5)unique
	如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的

(6)choices
	由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。
 
related_name       --> 基于对象反向查询,用于替换表名小写_set
related_query_name --> 基于双下划线反向查询,用于替换表名小写

IntegerField chiose 在页面中直接显示结束
    <https://blog.csdn.net/orangleliu/article/details/40268093>
# 字段
	chiose = ((1, "tcp"), (2, "udp"), (3, "icmp"))
    protocol = models.IntegerField(choices=chiose, default=1)

# 获取语法  表.get_表字段_display()    # mapping表名.get_proto字段_display
# 页面  <td>{{ mapping.get_protocol_display }}</td>

2.3、关系字段

ForeignKey	# 外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 '一对多'中'多'的一方。
	ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。
	to:	设置要关联的表
	to_field: 设置要关联的表的字段
	related_name: 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
	related_query_name: 反向查询操作时,使用的连接前缀,用于替换表名。

    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(可执行对象)

   db_constraint: 是否在数据库中创建外键约束,默认为True, # 实际环境中尽量少使用外键
    
# 索引及元数据
 class UserInfo(models.Model):
     nid = models.AutoField(primary_key=True)
     username = models.CharField(max_length=32)
     class Meta:
         # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
         db_table = "table_name"

        # 联合索引
        index_together = [
            ("pub_date", "deadline"),
        ]

        # 联合唯一索引
        unique_together = (("driver", "restaurant"),)

        # admin中显示的表名称
        verbose_name

        # verbose_name加s
        verbose_name_plural

2.4、其它文件中操作模型

创建一个test.py文件, 直接操作模型用于测试
import os
import django


if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day76.settings')
    django.setup()

    from app01 import models
	.........

2.5、defer,only

一般用于数据库优化

​ only: 只取指定的字段,返回值是queryset里套对象,对象只有指定的字段

​ defer: 不取指定的字段,返回值是queryset里套对象,对象只有指定的字段

三、单表增删改查

3.1、queryset对象说明

# 正常操作,打印都是querySet对象, 可以重修改模型 增加 __str__返回结果
ret = models.picture.objects.filter(name='linux')
print(ret)  # <QuerySet [<picture: picture object (2)>]>

# 模型中增加这两行
    def __str__(self):
        return "name: {0} price: {1}".format(self.name, self.price)

# 返回的结果就是 <QuerySet [<picture: name: linux price: 33.22>]>

3.2、增删改查

  1. 增加
# 增 方法1
models.Book.objects.create(name="nginx架构解析",price=22.22, publish="上海出版")

# 增 方法2
name = models.Book(name="python3标准库", price=33.33, publish="机械工业出版")
name.save()

# 通过循环,添加多条记录用于测试
for i in range(1, 10):
    prices = random.randrange(11, 99)
    x = models.Book(name="test-{}".format(i), price="{0}".format(prices), publish="北京出版")
    x.save()
  1. 修改
# 改 方法1 : 如果有多个相同的名称,那么修改的就会是全部,没有加where条件
    models.Book.objects.filter(name="test-7").update(publish="北京出版")

# 改 方法2
    t8 = models.Book.objects.filter(name="test-8").first()
    t8.publish = "南京出版"
    t8.save()
  1. 删除
# 删除, 最好是有多个条件,如果只是test-8那么全部的都会被删除
	models.Book.objects.filter(name="test-8", id=12).delete()
  1. 查询API

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

# querySet对象,直接调用 值.query能打印出对应sql语句。
#  默认打印就是一个query对象,如果想直接看结果,需要在模型上 定义 def __str__(self) 方法

# 查询 all()  queryset对象 <QuerySet [<Book: 名称: nginx架构解析 价格:22.22 出版:上海出版>,]
print(models.Book.objects.all())

# filter()
    f3 = models.Book.objects.filter(name="python3标准库")
    # print(f3.query)  #  WHERE `app01_book`.`name` = python3标准库

    f4 = models.Book.objects.filter(name="test-4", id=10) # 需要同时满足,没有就是一个[]
    # 相当于是 WHERE (`app01_book`.`id` = 10 AND `app01_book`.`name` = test - 4)

# get 返回筛选的结果,只能是一个,如果有多个会直接raise, 最好用来查ID之类的数据
    t7 = models.Book.objects.get(name="test-7")
    print(t7)    # 如果有>1个结果会直接报错, object
    # print(t7.query)   # 注意 object没有 query属性

# exclude   不要当前查找的,其它全部显示
    t8= models.Book.objects.exclude(name="test-8")
    print(t8)   # 返回不满足的全部条件
    print(t8.query)   # WHERE NOT (`app01_book`.`name` = test - 8)

# order_by   查询结果排序  对价格进行降序
    t9 = models.Book.objects.order_by("price")  # 直接以价格为排序
    print(t9.query)  # ORDER BY	`app01_book`.`price` ASC

    t10 = models.Book.objects.order_by("-price")    # 倒序, 价格高的在前
    print(t10.query) # ORDER BY	`app01_book`.`price` DESC

# reverse  感觉没啥用
    # ret = models.picture.objects.all().reverse()
    # print(ret)

    # count  统计有多少行
    # ret = models.picture.objects.all().filter(name="python").count()
    # print(ret)

    # first  取第一个值   last 取最后一个值
    # ret = models.picture.objects.first()
    # print(ret)
    # ret = models.picture.objects.last()
    # print(ret)

    # exists  如果querySet存在就返回True, 否则就是False
    # ret = models.picture.objects.filter(name="xiong").exists()
    # print(ret)   # 随便写一个不存在的返回就是 False, 这个很有用 ******

    # values 返回一个特殊的querySet字典
    # ret = models.picture.objects.all().values()
    # print(ret)
    # < QuerySet[{'id': 1, 'name': 'python', 'price': Decimal('22.22'), 'author': 'xiong',
     #           'create_date': datetime.date(2019, 4, 3)},] >

    # values_list   返回的与values差不多, 只不过它这个是一个列表加元组
    # ret = models.picture.objects.all().values_list()
    # print(ret)
    # < QuerySet[(1, 'python', Decimal('22.22'), 'xiong', datetime.date(2019, 4, 3)),(....)]>

3.3、模糊查询(双下划线)

  1. 取范围 in\range

    # in 10,50  那就是取这两个值,而不是取一个范围
        m1 = Book.objects.filter(price__in=[10,50])
        print(m1.query)     # WHERE	`app01_book`.`price` IN (10, 50)
    
    # range 这个就是取范围了, 从10到100之内的数
        m2 = Book.objects.filter(price__range=[10,50])
        print(m2.query)  # WHERE `app01_book`.`price` BETWEEN 10 AND 50
    
  2. 比大小

# 大于 gt 小于 lt  大于等于 gte 小于等于 lte
    m3 = Book.objects.filter(price__gt=20.22)
    print(m3.query)  # WHERE `app01_book`.`price` > 20.2200

    m4 = Book.objects.filter(price__lt=80)
    print(m4.query)  # WHERE `app01_book`.`price` < 80

    m5 = Book.objects.filter(price__gte=50)
    print(m5.query)     # WHERE `app01_book`.`price` >= 50

    m6 = Book.objects.filter(price__lte=50)
    print(m6.query)     # WHERE `app01_book`.`price` <= 50
  1. 开头结尾字符串
# name__endswith 以9结尾的字符串
    m9 = Book.objects.filter(name__endswith="9")
    print(m9.query)     # WHERE `app01_book`.`name` LIKE BINARY %9

# name__startswith 以linux开头的字符串
    m10 = Book.objects.filter(name__startswith="linux")
    print(m10.query)     # WHERE `app01_book`.`name` LIKE BINARY linux%
  1. 模糊匹配字符串
# name__contains 包含 test的字符串
    m7 = Book.objects.filter(name__contains="test")
    print(m7.query)     # WHERE `app01_book`.`name` LIKE BINARY %test%

# name__icontains 不区分大小写
    m8 = Book.objects.filter(name__icontains="test")
    print(m8.query)     # WHERE `app01_book`.`name` LIKE %test%
  1. 练习
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    publish = models.CharField(max_length=64)
    create_date = models.DateField(auto_now_add=True)

    def __str__(self):
        return "名称: {} 价格:{} 出版:{}".format(self.name, self.price, self.publish)

# 创建10条, 出版社后续手动在改
for i in range(1, 10):
    prices = random.randrange(11, 99)
    x = Book(name="test-{}".format(i), price="{0}".format(prices), publish="北京出版")
    x.save()

# 1 查询出版社出版过的价格大于200的书籍
    print(Book.objects.filter(price__gt=200))

# 2 查询2021年1月出版的所有以py开头的书籍名称
    p1 = Book.objects.filter(create_date__year=2021, create_date__month=1, name__startswith="py")
    print(p1)
    print(p1.query)

# 3 查询价格为50,100或者150的所有书籍名称及其出版社名称
    p2 = Book.objects.filter(price__in=[50,100,150]).values("name", "publish")
    print(p2)

# 4 查询价格在100到200之间的所有书籍名称及其价格
    p3 = Book.objects.filter(price__range=[100,200])
    print(p3)

# 5 查询所有北京出版出版社的书籍的价格(从高到低排序,去重)
    p4 = Book.objects.filter(publish="北京出版").order_by("name").distinct()
    print(p4)

# 查找所有书名里包含python的书

       ret = models.Book.objects.filter(name__startswith='python').values()

# 查找价格大于20元的书
    ret = models.Book.objects.filter(price__gt='20').values()

# 查找价格小于10元的书名和价格, 以及出版社
    ret = models.Book.objects.filter(price__lt='15').values('name','price','publish__addr')

# 查找在北京的出版社以及查看出版过的书
    ret = models.Publish.objects.filter(name='北京').values('book__name')
       ret = models.Book.objects.filter(publish__name='北京').values('name')

# 查找名字以上海开头的出版社
    ret = models.Publish.objects.filter(addr__startswith='上海')

# 查找作者名字里面带“t”字的作者
    ret = models.Author.objects.filter(name__startswith='t').values()

# 查找姓名是男的作者
    ret = models.Author.objects.filter(author__sex=1).values('name')

# 查找手机号是135开头的作者
    ret = models.Author.objects.filter(author__phone__startswith='135').values()
    ret = models.Authordetail.objects.filter(phone__startswith='135').values()

# 查找手机号是135开头的作者的姓名
    ret = models.Author.objects.filter(author__phone__startswith='135').values('name')

# 查找书名是“pyton”的书的出版社
    ret = models.Book.objects.filter(name='python').values('publish__addr')

# 查找书名是“pyton”的书的所有作者
    ret = models.Book.objects.filter(name__startswith='pyth').values('author__name')

# 查找书名是“pyton”的书的作者的姓名
    ret = models.Book.objects.filter(name__startswith='pytho').values('author__author__sex')

# 查找书名是“pyton”的书的作者的手机号码
    ret = models.Author.objects.filter(book__name='python').values('author__phone')

四、ORM多表

4.1、表结构-示例

from django.db import models

class Publish(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    address = models.CharField(max_length=128)

# 一对多的关系要建立在多的一方
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    price = models.DecimalField(max_digits=7, decimal_places=2)
    publish = models.ForeignKey(to="Publish", to_field="id", on_delete=models.CASCADE)
    auther = models.ManyToManyField(to="Auther")

class Auther(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    address = models.CharField(max_length=128)
    autherdetail = models.OneToOneField(to="Autherdetail", on_delete=models.CASCADE, to_field="id")

class Autherdetail(models.Model):
    id = models.AutoField(primary_key=True)
    phone = models.IntegerField(max_length=11)
    email = models.EmailField()
    
# 当makemigrations之后, 会自动生成五张表
    multipath_auther
    multipath_autherdetail
    multipath_book
    multipath_book_auther
    multipath_publish

# on_delete选项
    models.CASCADE  	# 级联删除, 1.X默认是级联删除 
    models.SET_NULL 	# 设制为空
    models.DO_NOTHING   # 什么都不做

在这里插入图片描述

4.1、字段说明

  1. 一对一

    # to="表名"   加引号,这个表能找着就行, 不加引号就需要定义在OneToOnefield之前
    # 会自带唯一性约束, 如果没有id会自动加上pid
    models.OneToOneField(to="Autherdetail", on_delete=models.CASCADE, to_field="id")
    
  2. 一对多

    # 需要注意的是: 一旦确认一对多的关系,建立连接需要在多的一方
    models.ForeignKey(to="Publish", to_field="id", on_delete=models.CASCADE)
    
  3. 多对多

    # MaryToMaryField会自动创建第三张表, 多对多会自动创建第三张表
    models.ManyToManyField(to="Auther")
    

4.2、数据库添加、删除

pk=number, pk是直接查询的主键

4.2.1、一对一

一对一 添加 修改 与 一对多 的添加 添加 删除 一样

  1. 添加

    # 一对一,需要先添加 详细页表 然后在关连
        Autherdetail.objects.create(phone=1338888888, email="11@qq.com")
        Autherdetail.objects.create(phone=1348888999, email="22@qq.com")
        Autherdetail.objects.create(phone=1341111333, email="33@qq.com")
    
    # 添加方式一
        Auther.objects.create(name="小黑", address="上海", autherdetail_id=1)
    
    # 添加方式二, 需要注意的是  a1需要是一个object对象,而不是一个queryset对象
        a1 = Autherdetail.objects.filter(pk=2).first()
        Auther.objects.create(name="小红", address="南京", autherdetail=a1)
    
  2. 修改

    # 一对一 不能重复 Duplicate entry
    
    # 方法一, 需要注意的是添加一定是object对象,而不是queryset对象 
    Auther.objects.filter(name="小红").update(autherdetail_id=3)
    
    # 方法二 
    a2 = Autherdetail.objects.filter(pk=2).first()
    Auther.objects.filter(name="小红").update(autherdetail=a2)
    
    # 方法三, 通过对象来修改
    a3 = Autherdetail.objects.filter(pk=3).first()
    au1 = Auther.objects.get(pk=1)
    au1.autherdetail = a3
    au1.save()
    
  3. 删除

    # 直接删除 并不会删除关连表的数据
    
    # 方法一: 通过queryset对象的 可以直接.下去
        Auther.objects.filter(pk=1).delete()
    
    # 方法二: object对象,通过.delete属性删除
        au2 = Auther.objects.get(pk=3)
        au2.delete()
    
    # 如果直接删除关连的字段,那么作者表的关连关系,通过on_delete=models.CASCADE也会被直接删除,慎用
        Autherdetail.objects.filter(pk=3).delete()  # 级联删除
    
4.2.2、多对多
  1. 添加

    1、 先创建一条记录,
    2、 找着对应要关连的字段
    3、 将它们关连起来
    
    # 添加多表,需要先将数据的对象取出来, 然后在进行关连
        b1 = Book.objects.filter(name="持续交付").first()
        b2 = Book.objects.filter(name="python标准库").first()
    
        a1 = Auther.objects.get(pk=2)   # 严格注意,一定取的是object 不是queryset
        a2 = Auther.objects.get(pk=4)
        a3 = Auther.objects.get(pk=5)
    
        # 添加单条记录
        b1.auther.add(a3)
    	# 将表与表的字段进行关连
        b1.auther.add(a1,a2)
        b2.auther.add(a1,a2)
    
  2. 删除

    # 没有修改,需要先删除,然后在添加,
    # 删除多条是remove 可以是ID也可以是对象, 可以传多个,但不要混着用
    
    # 删除单个或多条记录
        # b1.auther.remove(Auther.objects.get(pk=5))
        # b1.auther.remove(2,4)
    
    # 会将b1的数据全部清空, 这个慎用!
        b1.auther.clear()
    
    # 先清空 b1的数据,然后在添加, 这要注意, 传的是一个可迭代对象 列表元组之类的
        b1.auther.set([a3,])
    

五、数据库查询-重要

1、关连自段在哪个表, 由这个表开始查询的就是正向查询  比如A 关连B ,由A开始查询, A就是正向查询
2、基于对象的查询是 子查询  inner join 

5.1、基于对象的跨表查询

  1. 一对一

    # 表示例, 正反向查询
    class Auther(models.Model):
        autherdetail = models.OneToOneField(to="Autherdetail", on_delete=models.CASCADE, to_field="id")
    
    class Autherdetail(models.Model):
    
    # 正向查询: Auther --> 关连Auther  --> Autherdetail	--> 正向查询按字段
    # 反向查询: Autherdetail --> Auther					 --> 反向查询按表名小写
    

    示例

    # 正向 根据名称 查找邮箱,  一对一
    # 注意,filter查找出来的是queryset对象,而继续跨表查询 需要的是一个 object
    email = Auther.objects.filter(name="小红").first().autherdetail.email
    print(email)
    
    # 反向查询 根据 邮箱 查找 对应的作者信息,  获取的都是对象 object
    a1 = Autherdetail.objects.get(pk=2).auther
    print(a1.name)
    
  2. 一对多

    # 一对多的关系要建立在多的一方,  外键关系的建立是为了不写脏数据,数据重复建立不起来
    class Book(models.Model):
        publish = models.ForeignKey(to="Publish", to_field="id", on_delete=models.CASCADE)
    
    class Publish(models.Model):
    
    # 正向查询: Book --> 关连Publish  --> Publish	--> 正向查询按字段
    # 反向查询: Publish --> Book			     --> 反向查询按表名小写_set.all()
    

    示例

    # 正向 根据 书名 查找出版社  一对多 
    p1 = Book.objects.filter(name="持续交付").first().publish.name
    # print(p1.name)   # 如果没有 publish.name 就p1.name
    
    # 反向 根据 出版社 查找对应的书藉, 而单表查的时候 需要是一个 object
    # 注意: 反向是  book_set, 不管是多条还是单条都是 .all() 
    # 最后加上.values('字段') 可直接查看列表
    p2 = Publish.objects.filter(name="人民邮电出版社").first().book_set.all()
    print(p2)       # <QuerySet [<Book: Book object (1)>, <Book: Book object (5)>]>
    print([p.name for p in p2])	# 直接通过列表生成式打印
    
  3. 多对多

    class Book(models.Model):
        auther = models.ManyToManyField(to="Auther")
    
    class Auther(models.Model):
    
    # 正向查询: Book --> 关连Auther  --> Auther	--> 正向查询按字段
    # 反向查询: Auther --> Book					 --> 反向查询按表名小写_set.all()
    

    示例

    # 正向, 根据 书名查找 对应的作者 多对多
    b1 = Book.objects.filter(name="持续交付").first().auther.all()
    # 查找出所有的 作者信息,一个queryset对象<QuerySet [<Auther: Auther object (5)>]>
    print(b1)
    
    # 反向 根据作者 查找出对应的书
    b2 = Auther.objects.filter(name="小红").first().book_set.all()
    print([b.name for b in b2])
    
    # 继续查找出 出版社, 根据书查出版社就是 正向查询
    print([b.publish.name for b in b2])
    

5.2、基于双下划线的查询

基于双下划线的查询是 连表查询

  1. 一对一

    # 以小红为例, name=小红 pk=2
    # 以 auther为基表, 正向查询, 直接字段名称
    a1 = Auther.objects.filter(name="小红").values("autherdetail__phone")
    print(a1)   # <QuerySet [{'autherdetail__phone': 1348888999}]>
    
    # 以autherdetail为基表,反向查询,按表名小写
    a2 = Autherdetail.objects.filter(auther__id=2).values("phone")
    a3 = Autherdetail.objects.filter(phone="1348888999").values("auther__name")
    <QuerySet [{'phone': 1348888999}]>
    <QuerySet [{'auther__name': '小红'}]>
    
    # 示例 查找 电话是 1348888999 的作者名称,出版的书以及出版社
    # auther__book__publish__name, 只要有 就直接双下划线往下加
    a4 = Autherdetail.objects.filter(phone="1348888999").values("auther__name", "auther__book__name", "auther__book__publish__name")
    
  2. 一对多

    # 正向 查找 持续交付书的 出版社
    b1 = Book.objects.filter(name="持续交付").values("publish__name")
    
    # 反向
    b2 = Publish.objects.filter(book__name="持续交付").values("name")
    
    # 基结果都是queryset对象
    <QuerySet [{'publish__name': '人民邮电出版社'}]>
    <QuerySet [{'name': '人民邮电出版社'}]>
    
    # 查询 人民邮电出版社 出版过的所有书籍的名字与价格
    b3 = Publish.objects.filter(name="人民邮电出版社").values("book__name","book__price")
    b4 = Book.objects.filter(publish__name="人民邮电出版社").values("name", "price")
    
  3. 多对多

    # 查找出版社是 人民邮电出版社 出版的作者姓名,邮件,地址
    b5 = Publish.objects.filter(name="人民邮电出版社").values("book__auther__name", "book__auther__autherdetail__email", "book__auther__address")
    
    # 查询 小红 出过的所有书籍的名字, 以及对应的出版社
    b6 = Auther.objects.filter(name="小红").values("book__name", "book__publish__name")
    
    # 查看手机号码134开头的作者, 书以及出版社
    b7 = Autherdetail.objects.filter(phone__startswith="134").values("auther__book__name", "auther__book__publish__name")
    

5.3、聚合函数 aggregate

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

# 语法  queryset.aggregate(这里加函数), 打印的结果是字典

# 先导入方法
from django.db.models import Avg, Sum, Count, Max, Min

# 统计作者总数  {'c': 3}
x1 = Auther.objects.aggregate(c=Count("name"))

# 统计所有图书的价格 {'c': Decimal('276.54')}
x2 = Book.objects.aggregate(c=Sum("price"))

# 统计所有图书的平均,最高,最低价格 {'a': Decimal('92.'), 'max': Decimal('99.11'), 'min': Decimal('87.59')}
x3 = Book.objects.aggregate(a=Avg("price"), max=Max("price"), min=Min("price"))

5.4、分组查询 annotate

# 重要说明: 
    # group by 谁, 就以谁做基表,filter过滤, annotate取分组, values取值
    # values在前,表示group by, 在后表示取值,  不加默认是values("pk")
    # filter在前,表示where条件, 在后表示having

# annotate()为调用的`QuerySet`中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
# 总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。 

示例

# 统计作者总数  {'c': 3}
    x1 = Auther.objects.aggregate(c=Count("name"))

# 统计所有图书的价格 {'c': Decimal('276.54')}  
# 全部就是 Book.objects.all().values("pk").aggregate(c=Sum("price")) 先values 在group by 
    x2 = Book.objects.aggregate(c=Sum("price"))

# 统计所有图书的平均,最高,最低价格 {'a': Decimal('92.'), 'max': Decimal('99.11'), 'min': Decimal('87.59')}
    x3 = Book.objects.aggregate(a=Avg("price"), max=Max("price"), min=Min("price"))

# 统计每一本书作者个数  <QuerySet [{'name': 'python标准库', 'c': 2}, .....]>
    x4 = Book.objects.all().annotate(c=Count("auther__name")).values("name", "c")

# 如果以作者为group by 那就是以作者为分组, 打印的结果就是每本书的总数
    x5 = Auther.objects.all().annotate(c=Count("book__name")).values("book__name", "c")

# 统计每一个出版社的最便宜的书
    x6 = Publish.objects.all().annotate(min=Min("book__price")).values("name", "min")

# 统计每一本以py开头的书籍的作者个数
    x7 = Book.objects.filter(name__startswith="py").annotate(c=Count("auther__name")).values("name", "c")

# 统计不止一个作者的图书:(作者数量大于一)
    x8 = Auther.objects.annotate(c=Count("book__name")).filter(c__gt=1).values("name","c")

# 根据一本图书作者数量的多少对查询集 QuerySet进行排序
    x9 = Auther.objects.annotate(c=Count("book__name")).order_by("name").values("name","c")

# 查询各个作者出的书的总价格并排序
    x10 = Auther.objects.annotate(csum=Sum("book__price")).order_by("-csum").values("name", "csum")

# 查询每个出版社的名称和书籍个数
    x11 = Publish.objects.annotate(pbook=Count("book__name")).values("name", "pbook")

5.5、F\Q

from django.db.models import F, Q

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

    # 阅读数大于评论数
        b1 = Book.objects.filter(read_num__gt=F("commit_num")).values("name")
        # SELECT `name` FROM `multipath_book` WHERE `read_num` > `commit_num`
    
    # 全部阅读数加50
        b2 = Book.objects.all().update(read_num=F("read_num")+50)
        # UPDATE `multipath_book` SET `read_num` = (`multipath_book`.`read_num` + 50);
    
    
  • Q: 为了表示与 & ,或 |,非 ~

    # 与&:  两个关系要同时满足 比如   5< 3 > 1   5大于3 并且 3大小1
    # 或|:  两者满足一个即可   (1>2) | (2<3)   一个false,一个true
    # 非~:  取反
    
    # 取出价格大小80并且小于90的书
        b3 = Book.objects.filter(Q(price__gt="80")&Q( price__lt="90")).values("name")
    

5.6、TruncMonth 时间切割

from django.db.models.functions import TruncMonth

# 按时间归档, 先切割时间  TruncMonth,分组之后 在进行统计
time_count = models.Article.objects.filter(blog=blog).annotate(t_m=TruncMonth('create_time')).values('t_m').annotate(
    count_t_m=Count('t_m')).values('t_m', 'count_t_m').order_by('-t_m')

10 = Auther.objects.annotate(csum=Sum(“book__price”)).order_by("-csum").values(“name”, “csum”)

查询每个出版社的名称和书籍个数

x11 = Publish.objects.annotate(pbook=Count("book__name")).values("name", "pbook")

### 5.5、F\Q

> from django.db.models import F, Q

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

  ```python
  # 阅读数大于评论数
      b1 = Book.objects.filter(read_num__gt=F("commit_num")).values("name")
      # SELECT `name` FROM `multipath_book` WHERE `read_num` > `commit_num`
  
  # 全部阅读数加50
      b2 = Book.objects.all().update(read_num=F("read_num")+50)
      # UPDATE `multipath_book` SET `read_num` = (`multipath_book`.`read_num` + 50);
  
  • Q: 为了表示与 & ,或 |,非 ~

    # 与&:  两个关系要同时满足 比如   5< 3 > 1   5大于3 并且 3大小1
    # 或|:  两者满足一个即可   (1>2) | (2<3)   一个false,一个true
    # 非~:  取反
    
    # 取出价格大小80并且小于90的书
        b3 = Book.objects.filter(Q(price__gt="80")&Q( price__lt="90")).values("name")
    

5.6、TruncMonth 时间切割

from django.db.models.functions import TruncMonth

# 按时间归档, 先切割时间  TruncMonth,分组之后 在进行统计
time_count = models.Article.objects.filter(blog=blog).annotate(t_m=TruncMonth('create_time')).values('t_m').annotate(
    count_t_m=Count('t_m')).values('t_m', 'count_t_m').order_by('-t_m')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值