Django_模型詳解

来源:https://www.cnblogs.com/ivanlee717/archive/2022/09/21/16716534.html
-Advertisement-
Play Games

Django_模型ORM Django中內嵌了ORM框架,不需要直接編寫SQL語句進行資料庫操作,而是通過定義模型類,操作模型類來完成對資料庫中表的增刪改查和創建等操作。 O是object,也就類對象的意思。 R是relation,翻譯成中文是關係,也就是關係資料庫中數據表的意思。 M是mappin ...


Django_模型ORM

Django中內嵌了ORM框架,不需要直接編寫SQL語句進行資料庫操作,而是通過定義模型類,操作模型類來完成對資料庫中表的增刪改查和創建等操作。

O是object,也就類對象的意思。

R是relation,翻譯成中文是關係,也就是關係資料庫中數據表的意思。

M是mapping,是映射的意思。

image-20220915151922344

映射:

類:sql語句table表

類成員變數:table表中的欄位、類型和約束

類對象:sql表的表記錄

ORM的優點

  • 數據模型類都在一個地方定義,更容易更新和維護,也利於重用代碼。

  • ORM 有現成的工具,很多功能都可以自動完成,比如數據消除、預處理、事務等等。

  • 它迫使你使用 MVC 架構,ORM 就是天然的 Model,最終使代碼更清晰。

  • 基於 ORM 的業務代碼比較簡單,代碼量少,語義性好,容易理解。

  • 新手對於複雜業務容易寫出性能不佳的 SQL,有了ORM不必編寫複雜的SQL語句, 只需要通過操作模型對象即可同步修改數據表中的數據.

  • 開發中應用ORM將來如果要切換資料庫.只需要切換ORM底層對接資料庫的驅動【修改配置文件的連接地址即可】

ORM 也有缺點

  • ORM 庫不是輕量級工具,需要花很多精力學習和設置,甚至不同的框架,會存在不同操作的ORM。
  • 對於複雜的業務查詢,ORM表達起來比原生的SQL要更加困難和複雜。
  • ORM操作資料庫的性能要比使用原生的SQL差。
  • ORM 抽象掉了資料庫層,開發者無法瞭解底層的資料庫操作,也無法定製一些特殊的 SQL。【自己使用pymysql另外操作即可,用了ORM並不表示當前項目不能使用別的資料庫操作工具了。】

我們可以通過以下步驟來使用django的資料庫操作

1. 配置資料庫連接信息
2. 在models.py中定義模型類
3. 生成資料庫遷移文件並執行遷文件[註意:數據遷移是一個獨立的功能,這個功能在其他web框架未必和ORM一塊的]
4. 通過模型類對象提供的方法或屬性完成數據表的增刪改查操作

配置資料庫信息

在settings.py中保存了資料庫的連接配置信息,Django預設初始配置使用sqlite資料庫。

  1. 使用MySQL資料庫首先需要安裝驅動程式

    pip install PyMySQL
    
  2. 在Django的工程同名子目錄的__init__.py文件中添加如下語句

    from pymysql import install_as_MySQLdb
    install_as_MySQLdb() # 讓pymysql以MySQLDB的運行模式和Django的ORM對接運行
    

    作用是讓Django的ORM能以mysqldb的方式來調用PyMySQL。

  3. 修改database裡面的信息

    原本在setting中預設的是sqlite3版本的資料庫引擎,

    image-20220915160151560

    現在將其改為mysql資料庫的信息

    DATABASES = {
        "default" : {
            'ENGINE':'django.db.backends.mysql',
            'HOST': '127.0.0.1',  # 資料庫主機
            'PORT': 3306,  # 資料庫埠
            'USER': 'root',  # 資料庫用戶名
            'PASSWORD': '123456',  # 資料庫用戶密碼
            'NAME': 'student'  # 資料庫名字
        }
    }
    
  4. 在mysql中創建資料庫

    mysql> create database student default charset=utf8mb4;
    Query OK, 1 row affected (0.00 sec)
    

如果想列印orm轉換過程中的sql,需要在settings中進行如下配置:


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

定義模型類

定義模型類

  • 模型類被定義在"子應用/models.py"文件中。
  • 模型類必須直接或者間接繼承自django.db.models.Model類。

class Information(models.Model):

    choices =(
        (0,"單身"),
        (1,"有對象"),
        (2,"已婚")
    )

    name = models.CharField(max_length=20, db_index=True, verbose_name="姓名")
    age = models.SmallIntegerField(verbose_name="年齡")
    is_married = models.SmallIntegerField(choices=choices, default=0)
    profess = models.CharField(db_column="faculty", max_length=5, db_index=True,verbose_name="專業")
    description = models.TextField(default="", verbose_name="個性簽名")

    class Meta:
        db_table = 'Regina_information'

(1) 資料庫表名

模型類如果未指明表名db_table,Django預設以 小寫app應用名_小寫模型類名 為資料庫表名。

可通過db_table 指明資料庫表名。

(2) 關於主鍵

django會為表創建自動增長的主鍵列,每個模型只能有一個主鍵列。

如果使用選項設置某個欄位的約束屬性為主鍵列(primary_key)後,django不會再創建自動增長的主鍵列。

class Student(models.Model):
    # django會自動在創建數據表的時候生成id主鍵/還設置了一個調用別名 pk
    id = models.AutoField(primary_key=True, null=False, verbose_name="主鍵") # 設置主鍵

預設創建的主鍵列屬性為id,可以使用pk代替,pk全拼為primary key

(3) 屬性命名限制

  • 不能是python的保留關鍵字。

  • 不允許使用連續的2個下劃線,這是由django的查詢方式決定的。__ 是關鍵字來的,不能使用!!!

  • 定義屬性時需要指定欄位類型,通過欄位類型的參數指定選項,語法如下:

    屬性名 = models.欄位類型(約束選項, verbose_name="註釋")
    

(4)欄位類型

類型 說明
AutoField 自動增長的IntegerField,通常不用指定,不指定時Django會自動創建屬性名為id的自動增長屬性
BooleanField 布爾欄位,值為True或False
NullBooleanField 支持Null、True、False三種值
CharField 字元串,參數max_length表示最大字元個數,對應mysql中的varchar
TextField 大文本欄位,一般大段文本(超過4000個字元)才使用。
IntegerField 整數
DecimalField 十進位浮點數, 參數max_digits表示總位數, 參數decimal_places表示小數位數,常用於表示分數和價格 Decimal(max_digits=7, decimal_places=2) ==> 99999.99~ 0.00
FloatField 浮點數
DateField 日期
參數auto_now表示每次保存對象時,自動設置該欄位為當前時間。
參數auto_now_add表示當對象第一次被創建時自動設置當前。
參數auto_now_add和auto_now是相互排斥的,一起使用會發生錯誤。
TimeField 時間,參數同DateField
DateTimeField 日期時間,參數同DateField
FileField 上傳文件欄位,django在文件欄位中內置了文件上傳保存類, django可以通過模型的欄位存儲自動保存上傳文件, 但是, 在資料庫中本質上保存的僅僅是文件在項目中的存儲路徑!!
ImageField 繼承於FileField,對上傳的內容進行校驗,確保是有效的圖片

(5)約束選項

選項 說明
null 如果為True,表示允許為空,預設值是False。相當於python的None
blank 如果為True,則該欄位允許為空白,預設值是False。 相當於python的空字元串,“”
db_column 欄位的名稱,如果未指定,則使用屬性的名稱。
db_index 若值為True, 則在表中會為此欄位創建索引,預設值是False。 相當於SQL語句中的key
default 預設值,當不填寫數據時,使用該選項的值作為數據的預設值。
primary_key 如果為True,則該欄位會成為模型的主鍵,預設值是False,一般不用設置,系統預設設置。
unique 如果為True,則該欄位在表中必須有唯一值,預設值是False。相當於SQL語句中的unique

(6) 外鍵

在設置外鍵時,需要通過on_delete選項指明主表刪除數據時,對於外鍵引用表數據如何處理,在django.db.models中包含了可選常量:

  • CASCADE 級聯,刪除主表數據時連通一起刪除外鍵表中數據

  • PROTECT 保護,通過拋出ProtectedError異常,來阻止刪除主表中被外鍵應用的數據

  • SET_NULL 設置為NULL,僅在該欄位null=True允許為null時可用

  • SET_DEFAULT 設置為預設值,僅在該欄位設置了預設值時可用

  • SET() 設置為特定值或者調用特定方法,例如:

    from django.conf import settings
    from django.contrib.auth import get_user_model
    from django.db import models
    
    def get_sentinel_user():
        return get_user_model().objects.get_or_create(username='deleted')[0]
    
    class UserModel(models.Model):
        user = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            on_delete=models.SET(get_sentinel_user),
        )
    
  • DO_NOTHING 不做任何操作,如果資料庫前置指明級聯性,此選項會拋出IntegrityError異常

數據遷移

將模型類定義表架構的代碼轉換成SQL同步到資料庫中,這個過程就是數據遷移。django中的數據遷移,就是一個類,這個類提供了一系列的終端命令,幫我們完成數據遷移的工作。

(1)生成遷移文件

所謂的遷移文件, 是類似模型類的遷移類,主要是描述了數據表結構的類文件.

python manage.py makemigrations

image-20220915194917841

在app目錄下有一個migrations的文件夾,運行這個命令之後,可能會發生報錯

image-20220915200016987

這是因為在setting文件中沒有添加這個子應用的配置信息,需要手動補上

image-20220915200114841

image-20220915200130830

這樣的話,數據遷移就算成功了,同時migrations文件夾下會新生成一個文件0001_initial.py,但註意此時的資料庫是沒有參加這個操作的,所以裡面還是空的

# Generated by Django 3.2 on 2022-09-15 11:59

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Information',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(db_index=True, max_length=20, verbose_name='姓名')),
                ('age', models.SmallIntegerField(verbose_name='年齡')),
                ('is_married', models.SmallIntegerField(choices=[(0, '單身'), (1, '有對象'), (2, '已婚')], default=0)),
                ('profess', models.CharField(db_column='faculty', db_index=True, max_length=5, verbose_name='專業')),
                ('description', models.TextField(default='', verbose_name='個性簽名')),
            ],
            options={
                'db_table': 'Regina_information',
            },
        ),
    ]

(2)同步到資料庫中

python manage.py migrate

image-20220915200515047

image-20220915200542175

image-20220915200700812

這個主鍵就是自動生成的,其他的和我們最開始定義的一樣。

補充:在django內部提供了一系列的功能,這些功能也會使用到資料庫,所以在項目搭建以後第一次數據遷移的時候,會看到django項目中其他的數據表被創建了,但除了Regina_information,其他的並不重要。

在建立好數據之後,如果我們對資料庫結構有想法,比如將原有對faculty那一列刪掉,再加上一個新的省份的表

 #profess = models.CharField(db_column="faculty", max_length=5, db_index=True,verbose_name="專業")
  province = models.CharField(max_length=20, verbose_name="籍貫")

然後我們再去運行前兩個命令

image-20220915203253760

會發生這樣一個問題:新的province列需要添加一個預設值,這裡還給出了兩個修改的方法,一個是直接在命令行里添加,一個是退出運行在文件里添加

image-20220915203447094選擇1在命令行里添加好了之後提示數據遷移文件生成成功了

image-20220915203546180

然後再遷移到資料庫里

image-20220915203650172

image-20220915203710474

(3)添加記錄

首先建立好路由,然後視圖函數需要添加一個Information的類對象

def add(request):

    stu = Information("ivanlee",23,1,"nothing","shanxi")
    stu.save()

    return HttpResponse("success")

本身id值是自動產生的,但是如果類對象里不添加這個值,並且不寫清楚具體對應的參數名稱,就會造成找不到id值

image-20220915211157789

所以要把參數寫全

stu = Information(name="ivanlee", age=23,is_married = 1,description = "nothing",province = "shanxi")

image-20220915211312455

此時再去資料庫查看

image-20220915211350506

此時代碼中也可以進行列印,說明這些內容數據可以進行調用。

查詢數據

1. 基礎查詢

ORM中針對查詢結果的限制,提供了一個查詢集[QuerySet].這個QuerySet,是ORM中針對查詢結果進行保存數據的一個類型,我們可以通過瞭解這個QuerySet進行使用,達到查詢優化,或者限制查詢結果數量的作用。

1)all()

查詢所有對象,返回queryset對象。查詢集,也稱查詢結果集、QuerySet,表示從資料庫中獲取的對象集合。

  # 1. all(): 返回一個queryset對象
    res = Test.objects.all()
    return HttpResponse(res)

image-20220916152023613

此時返回了100個類對象,這種對象的名字叫做<class 'django.db.models.query.QuerySet'>,此時看不到所有內容和信息,所以在models文件里添加

    def __str__(self):
        return str(self.id)+": "+self.name + " "+str(self.age)+"\n"

這個內容是自定義的,方便返回值

image-20220916152514134

2) first()&&last()
stu1 = Test.objects.first()
print(stu1.name)
stu2 = Test.objects.last()
print(stu2.age)
3) filter()

這個函數等同於SQL語句中的where函數,括弧中添加條件

 stu = Test.objects.filter( id =12)
    print(stu)
    
>>>
(0.001) SELECT `db_student`.`id`, `db_student`.`name`, `db_student`.`sex`, `db_student`.`class`, `db_student`.`age`, `db_student`.`description`, `db_student`.`create_time`, `db_student`.`update_time` FROM `db_student` WHERE `db_student`.`id` = 12 LIMIT 21; args=(12,)

<QuerySet [<Test: 12: 張嘉睿 23<br>>]>

雖然這裡只返回了一個數據,但返回類型依然是queryset對象

image-20220916155450490

這裡過濾還有一個邏輯與的操作,不允許使用大於小於號或者邏輯或操作。例如

    stu = Test.objects.filter(id = 7, name="李藝帆")
    print(stu)

image-20220917110133758

4) exclude()

這個方法與filter方法相反

stu = Test.objects.exclude( age = 20)

取到的值均為年齡不是20歲的信息

image-20220917111111916

5)get()

返回與所給篩選條件相匹配的對象,返回結果有且只有一個, 如果符合篩選條件的對象超過一個或者沒有都會拋出錯誤。

student = Student.objects.get(pk=1)
print(student)
print(student.description)
get使用過程中的註意點:get是根據條件返回多個結果或者沒有結果,都會報錯
try:
    student = Student.objects.get(name="劉德華")
    print(student)
    print(student.description)
except Student.MultipleObjectsReturned:
    print("查詢得到多個結果!")
except Student.DoesNotExist:
    print("查詢結果不存在!")
6) order_by()

order_by("欄位") # 按指定欄位正序顯示,相當於asc從小到大

stu = Test.objects.all()
return HttpResponse(stu.order_by("age"))

order_by("-欄位") # 按欄位倒序排列,相當於 desc 從大到小

stu = Test.objects.all()
return HttpResponse(stu.order_by("-id"))

image-20220918104634916

order_by("第一排序","第二排序",...)

stu = Test.objects.all()
return HttpResponse(stu.order_by("id","-clas"))
7)count()

查詢集中對象的個數,返回一個個數

count = Student.objects.filter(sex=1).count()
print(count)
8)exists()

判斷查詢集中是否有數據,如果有則返回True,沒有則返回False

# 查詢Student表中是否存在學生
print(Student.objects.exists())
9)values()、values_list()
  • value()把結果集中的模型對象轉換成字典,並可以設置轉換的欄位列表,達到減少記憶體損耗,提高性能

  • values_list(): 把結果集中的模型對象轉換成列表,並可以設置轉換的欄位列表(元祖),達到減少記憶體損耗,提高性能

# values 把查詢結果中模型對象轉換成字典
stu = Test.objects.filter(age = 23)
print(stu.values())
print(stu.values("name","clas"))

image-20220918110057405

這裡返回的全部都是queryset集合,但是得到這個字典集合就可以進行序列化

print(json.dumps(list(stu.values("name","clas")),ensure_ascii=False))

image-20220918110439679

stu = Test.objects.filter(age = 23)
print(stu.values_list("name","clas"))

image-20220918111302308

10) distinct()

如果查詢到的數據里有重覆的值,使用這個方法可以進行去重,最簡單的來說,我們用1和2來區分性別,一共100條信息,那麼就會有100個數據,運用去重就得到了兩個值

 stu = Test.objects.values("sex")
  print(stu)
  print(stu.distinct())

image-20220918114454393

2. 模糊查詢

在基礎查詢中,我們只能使用age = 23或者id = 7這種全等匹配,導致我們無法去條件查詢獲取更多的數據。模糊查詢就可以彌補這一點

1)包含

說明:如果要包含%無需轉義,直接寫即可。這個和SQL語句中的like關鍵字功能一致

使用filter方法完成這一功能

    stu = Test.objects.filter(name__startswith = "張")
    stu = Test.objects.filter(name__endswith="帆")
    stu = Test.objects.filter(name__contains = "嘉")
WHERE `db_student`.`name` LIKE BINARY '張%' LIMIT 21; args=('張%',)
WHERE `db_student`.`name` LIKE BINARY '%帆' LIMIT 21; args=('%帆',)
WHERE `db_student`.`name` LIKE BINARY '%嘉%' LIMIT 21; args=('%嘉%',)

image-20220918131930401

2)isnull()

判斷所選項是否為空

stu = Test.objects.filter(description__isnull = False)
3) 比較查詢
  • gt 大於 (greater then)
  • gte 大於等於 (greater then equal)
  • lt 小於 (less then)
  • lte 小於等於 (less then equal)
stu = Test.objects.filter(id__gte = 7, id__lte = 12)

image-20220918132952976

stu = Test.objects.filter(age__gt = 22).order_by("id")

image-20220918133152522

上述的區域查詢也可以通過另一個參數range完成

stu = Test.objects.filter(id__range = (7,12))  #這裡都是閉區間
4)in()

表示一個或的關係,滿足一個就會獲取

stu = Test.objects.filter(id__in = [7,12])

image-20220918133833257

image-20220918133845719

5) 日期
stu = Test.objects.filter(create_time__year = 2020)
stu = Test.objects.filter(create_time__month = 11)

3. 高階查詢

1) F查詢

之前的查詢都是對象的屬性與常量值比較,兩個屬性怎麼比較呢? 答:使用F對象,被定義在django.db.models中。

查詢登記時間和更新時間不同的數據
stu = Test.objects.exclude(create_time = F("update_time"))

image-20220918145915890

發現這兩個數據不一樣

image-20220918145956591

2)Q查詢

多個過濾器逐個調用表示邏輯與關係,同sql語句中where部分的and關鍵字。

與:&

或:|

非:~

stu = Test.objects.filter(Q(id__gt = 10) | Q(age__gt = 20))  年齡大於20或者序號大於10
stu = Test.objects.filter(~Q(id__gt = 10) )  序號不大於10
3)聚合查詢

使用aggregate()過濾器調用聚合函數。聚合函數包括:Avg 平均,Count 數量,Max 最大,Min 最小,Sum 求和,被定義在django.db.models中。

res = Test.objects.aggregate(Avg("age"))
>>>{'age__avg': 20.47}
原生SQL語句為:SELECT AVG(`db_student`.`age`) AS `age__avg` FROM `db_student`; args=();

如果我們想按照自己的需求來起名,也可以修改為:

res = Test.objects.aggregate(DIY_avg = Avg("age"))

image-20220918152402042

查看最大的年齡和最小年齡

stu = Test.objects.aggregate(MAxage = Max("age"))
    stu = Test.objects.aggregate(minage=Min("age"))
4) 分組查詢
QuerySet對象.annotate()
# annotate() 進行分組統計,按前面select 的欄位進行 group by
# annotate() 返回值依然是 queryset對象,增加了分組統計後的鍵值對
模型對象.objects.values("id").annotate(course=Count('course__sid')).values('id','course')
# 查詢指定模型, 按id分組 , 將course下的sid欄位計數,返回結果是 name欄位 和 course計數結果 

# SQL原生語句中分組之後可以使用having過濾,在django中並沒有提供having對應的方法,但是可以使用filter對分組結果進行過濾
# 所以filter在annotate之前,表示where,在annotate之後代表having
# 同理,values在annotate之前,代表分組的欄位,在annotate之後代表數據查詢結果返回的欄位
stu = Test.objects.values("sex").annotate(avg_age = Avg("age")) 

image-20220918161202699

5)原生SQl

執行原生SQL語句,也可以直接跳過模型,才通用原生pymysql.

 ret = Student.objects.raw("SELECT id,name,age FROM db_student")  # student 可以是任意一個模型
 # 這樣執行獲取的結果無法通過QuerySet進行操作讀取,只能迴圈提取
stu = Test.objects.raw("select id,name,class from db_student where id > 6 and id < 13")
    print(stu)
    for item in stu:
        print(item,type(item))

image-20220918161742151

4. 修改記錄

# 1.基於模型類對象操作save
    stu = Test.objects.get(id = 12)
    print(stu.name, stu.age)
    stu.age = 22
    stu.save()

這種方法確實可以達到效果,但是根據sql語句來看,他會更新所有的數據,這樣會導致效率很慢

image-20220918162940828

UPDATE `db_student` SET `name` = '張嘉睿', `sex` = 1, `class` = 503, `age` = 22, `description` = '春去秋來,又一年。What did you get ?', `create_time` = '2020-11-20 10:00:00', `update_time` = '2020-12-20 10:00:00' WHERE `db_student`.`id` = 12; args=('張嘉睿', 1, 503, 22, '春去秋來,又一年。What did you get ?', '2020-11-20 10:00:00', '2020-12-20 10:00:00', 12); alias=default
# 2.queryset的update方法
Test.objects.filter(id = 7).update(age = 22, description = "我愛張嘉睿")
UPDATE `db_student` SET `age` = 22, `description` = '我愛張嘉睿' WHERE `db_student`.`id` = 7; args=(22, '我愛張嘉睿', 7); alias=default

這樣sql語句的效率會很高

現在如果將選中的所有的人的年齡都家三歲,並且修改掉個性簽名,這裡就用到之前所說的F函數和Q函數

Test.objects.filter(~Q(id = 7) & ~Q(id = 12)).update(age = F('age')+3, description = "張嘉睿嫁給李藝帆")

image-20220918164018850

5. 刪除記錄

# 1. 基於模型類刪除
Test.objects.get(pk = 100).delete()
# 2. 基於queryset刪除
Test.objects.filter(id = 99).delete()

創建關聯模型

先創建好幾張表

from django.db import models

# Create your models here.

class Clas(models.Model):
    name = models.CharField(max_length=32, verbose_name="班級名稱")

class Course(models.Model):
    title = models.CharField(max_length=32, verbose_name="班級名稱")

class Student(models.Model):

    sex_choices = (
        (0, "女"),
        (1, "男"),
        (2, "保密"),
    )
    name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
    age = models.SmallIntegerField(verbose_name="年齡", default=18)  # 年齡
    sex = models.SmallIntegerField(choices=sex_choices)
    birthday = models.DateField()

    #建立一對多的關係: 在資料庫中自動創建一個clas_id欄位
    clas = models.ForeignKey(to="Clas", on_delete=models.CASCADE, db_constraint=False)

    #多對多的關係:
    stu_course = models.ManyToManyField("Course",db_table="DIY_stu_course")

    #一對一關係: 建立關聯欄位
    stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE)


class StudentDetail(models.Model):
    tel = models.CharField(max_length=11)
    email = models.CharField(max_length=20)

一定記得要在setting文件同目錄下的init文件里添加欄位,不然就會報錯

image-20220919204811130

image-20220919205528380

由於多對多關係會建立一個新的表,所以一共有5張表,

mysql> desc DIY_stu_course;
+------------+------------+------+-----+---------+----------------+
| Field      | Type       | Null | Key | Default | Extra          |
+------------+------------+------+-----+---------+----------------+
| id         | bigint(20) | NO   | PRI | NULL    | auto_increment |
| student_id | bigint(20) | NO   | MUL | NULL    |                |
| course_id  | bigint(20) | NO   | MUL | NULL    |                |
+------------+------------+------+-----+---------+----------------+
mysql> desc regina_student;
+---------------+-------------+------+-----+---------+----------------+
| Field         | Type        | Null | Key | Default | Extra          |
+---------------+-------------+------+-----+---------+----------------+
| id            | bigint(20)  | NO   | PRI | NULL    | auto_increment |
| name          | varchar(32) | NO   | UNI | NULL    |                |
| age           | smallint(6) | NO   |     | NULL    |                |
| sex           | smallint(6) | NO   |     | NULL    |                |
| birthday      | date        | NO   |     | NULL    |                |
| clas_id       | bigint(20)  | NO   | MUL | NULL    |                |
| stu_detail_id | bigint(20)  | NO   | UNI | NULL    |                |
+---------------+-------------+------+-----+---------+----------------+

關聯添加

1)一對一和一對多

因為課程,班級都是獨立的數據,所以可以簡單手動創建,但是學生表需要關聯添加

stu = Student.objects.create(name="regina",age=22,sex=0, birthday="2000-01-27", clas_id = 1, stu_detail_id=1)
    print(stu)
    print(stu.name)

    # stu.clas是一個模型類
    print(stu.clas)

    #所以想獲取學生的班級信息直接調用stu.clas
    print(stu.clas.name)
>>>
Student object (1)
regina
Clas object (1)
python127

image-20220920140413262

2) 多對多關係

 # 多對多關聯記錄的增刪改查
stu = Student.objects.create(name="ivanlee",age=22,sex=1, birthday="1999-07-17", clas_id = 1, stu_detail_id=2)
c1 = Course.objects.get(title="basketball")
c2 = Course.objects.get(title="math")
stu.stu_course.add(c1,c2)

stu2 = Student.objects.get(name="regina")
c3 = Course.objects.get(title="valleyball")
stu2.stu_course.add(c2,c3)

因為學生與課程是多對多的關係,所以一個學生可以選擇多門課程,一個課程也可以被多名學生選中,因此c2可以被反覆添加

image-20220920162726145

多對多關係會單獨生成一張表,我給他起了名字叫做DIY_Stu_course

image-20220920162824545

其實添加方式也可以通過添加課程id值直接添加,或者是通過列表傳參的方式

stu2.stu_course.add(4)
stu.stu_course.add(*[3,4])

刪除

刪除操作

stu = Student.objects.get(name="regina")
stu.stu_course.remove()

如果想要刪除某個同學的全部選課,可以使用clear函數

stu.stu_course.clear()

如果想要清空並且重新選新的課,可以直接使用set函數,相當於set = clear + add

stu.stu_course.set(1,2)

查找

想要查詢學生課程的名稱使用all函數

stu = Student.objects.get(name="regina")
course = stu.stu_course.all()
print(course)

all函數所對應的sql語句為:

SELECT `regina_course`.`id`, `regina_course`.`title` FROM `regina_course` INNER JOIN `DIY_stu_course` ON (`regina_course`.`id` = `DIY_stu_course`.`course_id`) WHERE `DIY_stu_course`.`student_id` = 1 LIMIT 21; 

image-20220920165241500

關聯查詢

一對多

'''
    基於對象的關聯查詢(子查詢)
    '''
    # 查詢學生的課程名稱
    # 正向查詢:通過學生表的關聯屬性去查到關聯的表欄位

    stu = Student.objects.get(name = "regina")
    print(stu.clas.name)

    # 反向查詢方法1:查看某個班的學生有哪些
    # 要依賴一個表名的小寫和_set的組合,表示這是一個集合的形式
    class_ = Clas.objects.get(name = "python127")
    name_ = class_.student_set.all()
    print(name_)  #<QuerySet [<Student: regina>, <Student: ivanlee>]

上述反向查詢使用的名字是系統預設的命名方式,如果想要根據自己的要求寫名字,需要修改models文件

image-20220920172114528

最初就是在建立關係時使用的參數里沒有添加related_name欄位,現在設置為自己定義的可以直接進行查詢

class_ = Clas.objects.get(name = "python127")
name_ = class_.DIY_studetlist.all()

一對一

# 一對一的關聯查詢
    stu = Student.objects.get(name="regina")
    print(stu.stu_detail.email)

    email_ = StudentDetail.objects.get(tel=155)
    print(email_.student.name)

這裡的反向查詢直接輸入小寫的原表明即可,因為這是一對一關係,只會搜索到一個對象,但同樣可以使用related_name進行改寫

多對多

# 多對多的關聯查詢
    stu = Student.objects.get(name="regina")
    print(stu.stu_course.all())

    course_name = Course.objects.get(title="math")
    ret = course_name.student_set.all()
    print(ret)

多對多的方式基本上和一對多一樣

image-20220920173300855

現在把資料庫改一下

stu_course = models.ManyToManyField("Course",related_name="DIY_studentcourse",db_table="DIY_stu_course")
ret = course_name.DIY_studentcourse.all()
    print(ret.values("name","age"))

image-20220920173745805

join查詢

首先複習一下sql語句中的join用法

-- 查詢學生的姓名和所在班級的名稱

select regina_student.name, regina_clas.name from regina_student inner join regina_clas on regina_student.clas_id = regina_clas.id;

image-20220921111106285

如果通過多對多關聯表進行join

-- 查詢學生的姓名和選修的課程

select * from regina_student left join DIY_stu_course Dsc on regina_student.id = Dsc.student_id;

image-20220921155855859

這裡面並沒有出現課程的名稱,所以繼續連接另外一個表

select name, course_id, regina_course.title from regina_student left join DIY_stu_course on regina_student.id = DIY_stu_course.student_id inner join regina_course on regina_course.id = DIY_stu_course.course_id;

image-20220921160504131

基於雙下劃線查詢

# 正向查詢
    ret1 = Student.objects.filter(age__gt=20).values("name" , "clas__name")
    print(ret1)
    # 反向查詢
    ret2 = Clas.objects.filter(DIY_studetlist__age__gt=20).values("DIY_studetlist__name","name")
    print(ret2)
    
    
<QuerySet [{'name': 'regina', 'clas__name': 'python127'}, {'name': 'ivanlee', 'clas__name': 'python127'}]>
<QuerySet [{'DIY_studetlist__name': 'regina', 'name': 'python127'}, {'DIY_studetlist__name': 'ivanlee', 'name': 'python127'}]>
多對多
stu = Student.objects.filter(name = "regina").values("name","stu_course__title")
    print(stu)
<QuerySet [{'name': 'regina', 'stu_course__title': 'valleyball'}, {'name': 'regina', 'stu_course__title': 'history'}, {'name': 'regina', 'stu_course__title': 'math'}]>
#查詢選了數學的學生姓名
    cou = Course.objects.filter(title="math").values("DIY_studentcourse__name")
<QuerySet [{'DIY_studentcourse__name': 'ivanlee'}, {'DIY_studentcourse__name': 'regina'}]>
#查詢所有學生的姓名和手機號
    stu = Student.objects.filter().values("name", "stu_detail__tel")
<QuerySet [{'name': 'regina', 'stu_detail__tel': '155'}, {'name': 'ivanlee', 'stu_detail__tel': '1888'}]>

這些都是有相關聯的表都查詢,如果兩個表之間沒有直接關係,就需要藉助一個中間表進行聯繫

#查詢手機號為110的學生姓名以及班級名稱
    stu = StudentDetail.objects.filter(tel="155").values("student__name","student__clas__name")
<QuerySet [{'student__name': 'regina', 'student__clas__name': 'python127'}]>

-------或者---------
stu = Student.objects.filter(stu_detail__tel="155").values("name","clas__name")

以下就是如果碰到關聯查詢並且還需要分組:

-- 查詢每個班的人數
select regina_clas.name, count(*) as "人數" from regina_student inner join regina_clas on clas_id = regina_clas.id group by regina_clas.name;

image-20220921170717607

select regina_clas.name, count(regina_student.name) as "人數" from regina_clas left join regina_student on clas_id = regina_clas.id group by regina_clas.name;

image-20220921170919570

換成ORM模型里的語言:

num = Clas.objects.values("name").annotate(人數 = Count("DIY_studetlist__name"))

<QuerySet [{'name': 'python127', '人數': 2}, {'name': 'java717', '人數': 0}, {'name': 'php1221', '人數': 0}, {'name': 'C++1999', '人數': 0}]>
# 查詢每一個學生的姓名和選的課程數目
    num = Student.objects.values("name").annotate(數目 = Count("stu_course__title"))
  
<QuerySet [{'name': 'ivanlee', '數目': 4}, {'name': 'regina', '數目': 3}]>
# 查詢每一個學生的姓名,年齡和選的課程數目
num = Student.objects.values("name","age").annotate(數目 = Count("stu_course__title"))
  ----優化後-----
num = Student.objects.all().annotate(no = Count("stu_course__title")) #列印num並不會生成數量,但是回會有新的屬性值no
print(num.values("name","age","no"))
  
<QuerySet [{'name': 'ivanlee', 'age': 22, 'no': 4}, {'name': 'regina', 'age': 22, 'no': 3}]>
# 每一個課程名稱以及選修學生的個數
    num = Course.objects.all().annotate(人數 = Count("DIY_studentcourse__name"))
    print(num.values("title","人數"))
    
<QuerySet [{'title': 'basketball', '人數': 1}, {'title': 'valleyball', '人數': 1}, {'title': 'computer', '人數': 1}, {'title': 'history', '人數': 2}, {'title': 'math', '人數': 2}]>
# 查詢選修課程個數大於1的學生姓名和手機號和課程個數 (sql中的having關鍵字)

    num = Student.objects.all().annotate(sum = Count("stu_course__title")).filter(sum__gt = 1)
    print(num.values("name","stu_detail__tel","sum"))
    
SELECT `regina_student`.`name`, `regina_studentdetail`.`tel`, COUNT(`regina_course`.`title`) AS `sum` FROM `regina_student` LEFT OUTER JOIN `DIY_stu_course` ON (`regina_student`.`id` = `DIY_stu_course`.`student_id`) LEFT OUTER JOIN `regina_course` ON (`DIY_stu_course`.`course_id` = `regina_course`.`id`) INNER JOIN `regina_studentdetail` ON (`regina_student`.`stu_detail_id` = `regina_studentdetail`.`id`) GROUP BY `regina_student`.`id`, `regina_studentdetail`.`tel` HAVING COUNT(`regina_course`.`title`) > 1 ORDER BY NULL LIMIT 21; args=(1,)

<QuerySet [{'name': 'regina', 'stu_detail__tel': '155', 'sum': 3}, {'name': 'ivanlee', 'stu_detail__tel': '1888', 'sum': 4}]>

還可以根據數據進行排序

# 查詢選修課程個數大於1的學生姓名和課程個數,並按照課程數排序

    num = Student.objects.all().annotate(sum = Count("stu_course__title")).order_by("sum")
    print(num.values("name","sum"))
    
<QuerySet [{'name': 'regina', 'sum': 3}, {'name': 'ivanlee', 'sum': 4}]>

按照首字母排序也可以

num = Student.objects.all().annotate(sum = Count("stu_course__title")).order_by("name")
    print(num.values("name","sum"))
 
<QuerySet [{'name': 'ivanlee', 'sum': 4}, {'name': 'regina', 'sum': 3}]>  

本文來自博客園,作者:ivanlee717,轉載請註明原文鏈接:https://www.cnblogs.com/ivanlee717/p/16716534.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 多用戶即時通訊系統02 4.編碼實現01 4.1功能實現-用戶登錄 4.1.1功能說明 因為還沒有學習資料庫,我們人為規定 用戶名/id = 100,密碼為 123456 就可以登錄,其他用戶不能登錄,後面使用HashMap模擬資料庫,這樣就可以多個用戶登錄。 4.1.2思路分析+框架圖 用戶的登錄 ...
  • 前言 secure boot 和FIT Image是前段時間接觸到的,其實早就該總結下了,奈何懶癌犯了,拖了好久才寫出來。 之前也有人問我,工作後最大的感受是什麼?我的回答是:“快速學習”。 就嵌入式來講,大多數應屆生在校期間可能都沒做過完整的項目,僅憑在校期間學習的內容很難勝任公司的要求。 就底層 ...
  • 寫在前面 其實media配置也可以完全用static代替(看你自己的選擇),static代替的方法是直接在mobles.py里設置用戶上傳頭像的時候,修改一下用戶上傳頭像時的保存位置 當設置成static/avatar/之後,我們後期在獲取用戶上傳的文件時就可以在其前面加一個static就可以獲取到 ...
  • 註冊中心可以說是微服務架構中的”通訊錄“,它記錄了服務和服務地址的映射關係。在分散式架構中,服務會註冊到這裡,當服務需要調用其它服務時,就到這裡找到服務的地址,進行調用。 ...
  • 案例 學習網址:https://seaborn.pydata.org/examples/errorband_lineplots.html import seaborn as sns import pandas as pd sns.set_theme(style="darkgrid") # 導入數據 ...
  • 前言 工欲善其事,必先利其器。本篇文章我們介紹下 Terraform,為後續創建各種雲資源做準備,比如Kubernetes 關鍵詞:IaC, Infrastructure as Code, Terraform, 基礎架構即代碼,Terraform 例子, Terraform 入門,Terraform ...
  • 顧名思義,子流程是一個包含其他活動、網關、事件等的活動,這些活動本身形成了一個流程,該流程是更大流程的一部分。 使用子流程確實有一些限制: 一個子流程只能有一個none類型的啟動事件,不允許有其他類型的啟動事件。一個子流程必須至少有一個結束事件。(請註意,BPMN 2.0 規範允許在子流程中省略開始 ...
  • Docker安裝和卸載 一,已安裝Docker,卸載Docker 1.方法一 sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-l ...
一周排行
    -Advertisement-
    Play Games
  • 一:背景 1.講故事 在分析的眾多dump中,經常會遇到各種奇葩的問題,僅通過dump這種快照形式還是有很多問題搞不定,而通過 perfview 這種粒度又太粗,很難找到問題之所在,真的很頭疼,比如本篇的 短命線程 問題,參考圖如下: 我們在 t2 時刻抓取的dump對查看 短命線程 毫無幫助,我根 ...
  • 在日常後端Api開發中,我們跟前端的溝通中,通常需要協商好入參的數據類型,和參數是通過什麼方式存在於請求中的,是表單(form)、請求體(body)、地址欄參數(query)、還是說通過請求頭(header)。 當協商好後,我們的介面又需要怎麼去接收這些數據呢?很多小伙伴可能上手就是直接寫一個實體, ...
  • 許多情況下我們需要用到攝像頭獲取圖像,進而處理圖像,這篇博文介紹利用pyqt5、OpenCV實現用電腦上連接的攝像頭拍照並保存照片。為了使用和後續開發方便,這裡利用pyqt5設計了個相機界面,後面將介紹如何實現,要點包括界面設計、邏輯實現及完整代碼。 ...
  • 思路分析 註冊頁面需要對用戶提交的數據進行校驗,並且需要對用戶輸入錯誤的地方進行提示! 所有我們需要使用forms組件搭建註冊頁面! 平時我們書寫form是組件的時候是在views.py裡面書寫的, 但是為了接耦合,我們需要將forms組件都單獨寫在一個地方,需要用的時候導入就行! 例如,在項目文件 ...
  • 思路分析 登錄頁面,我們還是採用ajax的方式提交用戶數據 唯一需要學習的是如何製作圖片驗證碼! 具體的登錄頁面效果圖如下: 如何製作圖片驗證碼 推導步驟1:在img標簽的src屬性里放上驗證碼的請求路徑 補充1.img的src屬性: 1.圖片路徑 2.url 3.圖片的二進位數據 補充2:字體樣式 ...
  • 哈嘍,兄弟們! 最近有許多小伙伴都在吐槽打工好難。 每天都是執行許多重覆的任務 例如閱讀新聞、發郵件、查看天氣、打開書簽、清理文件夾等等, 使用自動化腳本,就無需手動一次又一次地完成這些任務, 非常方便啊有木有?! 而在某種程度上,Python 就是自動化的代名詞。 今天就來和大家一起學習一下, 用 ...
  • 作者:IT王小二 博客:https://itwxe.com 前面小二介紹過使用Typora+PicGo+LskyPro打造舒適寫作環境,那時候需要使用水印功能,但是小二在升級LskyPro2.x版本發現有很多不如人意的東西,遂棄用LskyPro使用MinIO結合代碼實現自己需要的圖床功能,也適合以後 ...
  • OpenAI Gym是一款用於研發和比較強化學習演算法的工具包,本文主要介紹Gym模擬環境的功能和工具包的使用方法,並詳細介紹其中的經典控制問題中的倒立擺(CartPole-v0/1)問題。最後針對倒立擺問題如何建立控制模型並採用爬山演算法優化進行了介紹,並給出了相應的完整python代碼示例和解釋。要... ...
  • python爬蟲瀏覽器偽裝 #導入urllib.request模塊 import urllib.request #設置請求頭 headers=("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, l ...
  • 前端代碼搭建 主要利用的是bootstrap3中js插件里的模態框版塊 <li><a href="" data-toggle="modal" data-target=".bs-example-modal-lg">修改密碼</a></li> <div class="modal fade bs-exam ...