Django 数据库
一、操作數(shù)據(jù)庫
Django配置連接數(shù)據(jù)庫:
在操作數(shù)據(jù)庫之前,首先先要連接數(shù)據(jù)庫。這里我們以配置MySQL為例來講解。Django連接數(shù)據(jù)庫,不需要單獨的創(chuàng)建一個連接對象。只需要在settings.py文件中做好數(shù)據(jù)庫相關(guān)的配置就可以了。示例代碼如下:
DATABASES = {'default': {# 數(shù)據(jù)庫引擎(是mysql還是oracle等)'ENGINE': 'django.db.backends.mysql',# 數(shù)據(jù)庫的名字 'NAME': 'dfz', # 連接mysql數(shù)據(jù)庫的用戶名 'USER': 'root', # 連接mysql數(shù)據(jù)庫的密碼 'PASSWORD': 'root', # mysql數(shù)據(jù)庫的主機(jī)地址 'HOST': '127.0.0.1', # mysql數(shù)據(jù)庫的端口號 'PORT': '3306', } } 在Django中操作數(shù)據(jù)庫:
在Django中操作數(shù)據(jù)庫有兩種方式。第一種方式就是使用原生sql語句操作,第二種就是使用ORM模型來操作。這節(jié)課首先來講下第一種。
在Django中使用原生sql語句操作其實就是使用python db api的接口來操作。如果你的mysql驅(qū)動使用的是pymysql,那么你就是使用pymysql來操作的,只不過Django將數(shù)據(jù)庫連接的這一部分封裝好了,我們只要在settings.py中配置好了數(shù)據(jù)庫連接信息后直接使用Django封裝好的接口就可以操作了。示例代碼如下:
# 使用django封裝好的connection對象,會自動讀取settings.py中數(shù)據(jù)庫的配置信息
from django.db import connection# 獲取游標(biāo)對象
cursor = connection.cursor()
# 拿到游標(biāo)對象后執(zhí)行sql語句 cursor.execute("select * from book") # 獲取所有的數(shù)據(jù) rows = cursor.fetchall() # 遍歷查詢到的數(shù)據(jù) for row in rows: print(row) 以上的execute以及fetchall方法都是Python DB API規(guī)范中定義好的。任何使用Python來操作MySQL的驅(qū)動程序都應(yīng)該遵循這個規(guī)范。所以不管是使用pymysql或者是mysqlclient或者是mysqldb,他們的接口都是一樣的。更多規(guī)范請參考:https://www.python.org/dev/peps/pep-0249/。
Python DB API下規(guī)范下cursor對象常用接口:
-
description:如果cursor執(zhí)行了查詢的sql代碼。那么讀取cursor.description屬性的時候,將返回一個列表,這個列表中裝的是元組,元組中裝的分別是(name,type_code,display_size,internal_size,precision,scale,null_ok),其中name代表的是查找出來的數(shù)據(jù)的字段名稱,其他參數(shù)暫時用處不大。 -
rowcount:代表的是在執(zhí)行了sql語句后受影響的行數(shù)。 -
close:關(guān)閉游標(biāo)。關(guān)閉游標(biāo)以后就再也不能使用了,否則會拋出異常。 -
execute(sql[,parameters]):執(zhí)行某個sql語句。如果在執(zhí)行sql語句的時候還需要傳遞參數(shù),那么可以傳給parameters參數(shù)。示例代碼如下:cursor.execute("select * from article where id=%s",(1,)) -
fetchone:在執(zhí)行了查詢操作以后,獲取第一條數(shù)據(jù)。 -
fetchmany(size):在執(zhí)行查詢操作以后,獲取多條數(shù)據(jù)。具體是多少條要看傳的size參數(shù)。如果不傳size參數(shù),那么默認(rèn)是獲取第一條數(shù)據(jù)。 -
fetchall:獲取所有滿足sql語句的數(shù)據(jù)。
二、ORM模型介紹
隨著項目越來越大,采用寫原生SQL的方式在代碼中會出現(xiàn)大量的SQL語句,那么問題就出現(xiàn)了:
- SQL語句重復(fù)利用率不高,越復(fù)雜的SQL語句條件越多,代碼越長。會出現(xiàn)很多相近的SQL語句。
- 很多SQL語句是在業(yè)務(wù)邏輯中拼出來的,如果有數(shù)據(jù)庫需要更改,就要去修改這些邏輯,這會很容易漏掉對某些SQL語句的修改。
- 寫SQL時容易忽略web安全問題,給未來造成隱患。SQL注入。
ORM,全稱Object Relational Mapping,中文叫做對象關(guān)系映射,通過ORM我們可以通過類的方式去操作數(shù)據(jù)庫,而不用再寫原生的SQL語句。通過把表映射成類,把行作實例,把字段作為屬性,ORM在執(zhí)行對象操作的時候最終還是會把對應(yīng)的操作轉(zhuǎn)換為數(shù)據(jù)庫原生語句。使用ORM有許多優(yōu)點:
- 易用性:使用
ORM做數(shù)據(jù)庫的開發(fā)可以有效的減少重復(fù)SQL語句的概率,寫出來的模型也更加直觀、清晰。 - 性能損耗小:
ORM轉(zhuǎn)換成底層數(shù)據(jù)庫操作指令確實會有一些開銷。但從實際的情況來看,這種性能損耗很少(不足5%),只要不是對性能有嚴(yán)苛的要求,綜合考慮開發(fā)效率、代碼的閱讀性,帶來的好處要遠(yuǎn)遠(yuǎn)大于性能損耗,而且項目越大作用越明顯。 - 設(shè)計靈活:可以輕松的寫出復(fù)雜的查詢。
- 可移植性:
Django封裝了底層的數(shù)據(jù)庫實現(xiàn),支持多個關(guān)系數(shù)據(jù)庫引擎,包括流行的MySQL、PostgreSQL和SQLite。可以非常輕松的切換數(shù)據(jù)庫。
創(chuàng)建ORM模型:
ORM模型一般都是放在app的models.py文件中。每個app都可以擁有自己的模型。并且如果這個模型想要映射到數(shù)據(jù)庫中,那么這個app必須要放在settings.py的INSTALLED_APP中進(jìn)行安裝。以下是寫一個簡單的書籍ORM模型。示例代碼如下:
from django.db import models
class Book(models.Model): name = models.CharField(max_length=20,null=False) author = models.CharField(max_length=20,null=False) pub_time = models.DateTimeField(default=datetime.now) price = models.FloatField(default=0) 以上便定義了一個模型。這個模型繼承自django.db.models.Model,如果這個模型想要映射到數(shù)據(jù)庫中,就必須繼承自這個類。這個模型以后映射到數(shù)據(jù)庫中,表名是模型名稱的小寫形式,為book。在這個表中,有四個字段,一個為name,這個字段是保存的是書的名稱,是varchar類型,最長不能超過20個字符,并且不能為空。第二個字段是作者名字類型,同樣也是varchar類型,長度不能超過20個。第三個是出版時間,數(shù)據(jù)類型是datetime類型,默認(rèn)是保存這本書籍的時間。第五個是這本書的價格,是浮點類型。
還有一個字段我們沒有寫,就是主鍵id,在django中,如果一個模型沒有定義主鍵,那么將會自動生成一個自動增長的int類型的主鍵,并且這個主鍵的名字就叫做id。
映射模型到數(shù)據(jù)庫中:
在settings.py中,配置好DATABASES,做好數(shù)據(jù)庫相關(guān)的配置。
在app中的models.py中定義好模型,這個模型必須繼承自django.db.models。
將這個app添加到settings.py的INSTALLED_APP中。
在命令行終端,進(jìn)入到項目所在的路徑,然后執(zhí)行命令python manage.py makemigrations來生成遷移腳本文件。
同樣在命令行中,執(zhí)行命令python manage.py migrate來將遷移腳本文件映射到數(shù)據(jù)庫中
三、模型常用屬性
常用字段:
在Django中,定義了一些Field來與數(shù)據(jù)庫表中的字段類型來進(jìn)行映射。以下將介紹那些常用的字段類型。
AutoField:
映射到數(shù)據(jù)庫中是int類型,可以有自動增長的特性。一般不需要使用這個類型,如果不指定主鍵,那么模型會自動的生成一個叫做id的自動增長的主鍵。如果你想指定一個其他名字的并且具有自動增長的主鍵,使用AutoField也是可以的。
BigAutoField:
64位的整形,類似于AutoField,只不過是產(chǎn)生的數(shù)據(jù)的范圍是從1-9223372036854775807。
BooleanField:
在模型層面接收的是True/False。在數(shù)據(jù)庫層面是tinyint類型。如果沒有指定默認(rèn)值,默認(rèn)值是None。
CharField:
在數(shù)據(jù)庫層面是varchar類型。在Python層面就是普通的字符串。這個類型在使用的時候必須要指定最大的長度,也即必須要傳遞max_length這個關(guān)鍵字參數(shù)進(jìn)去。
DateField:
日期類型。在Python中是datetime.date類型,可以記錄年月日。在映射到數(shù)據(jù)庫中也是date類型。使用這個Field可以傳遞以下幾個參數(shù):
auto_now:在每次這個數(shù)據(jù)保存的時候,都使用當(dāng)前的時間。比如作為一個記錄修改日期的字段,可以將這個屬性設(shè)置為True。auto_now_add:在每次數(shù)據(jù)第一次被添加進(jìn)去的時候,都使用當(dāng)前的時間。比如作為一個記錄第一次入庫的字段,可以將這個屬性設(shè)置為True。
DateTimeField:
日期時間類型,類似于DateField。不僅僅可以存儲日期,還可以存儲時間。映射到數(shù)據(jù)庫中是datetime類型。這個Field也可以使用auto_now和auto_now_add兩個屬性。
TimeField:
時間類型。在數(shù)據(jù)庫中是time類型。在Python中是datetime.time類型。
EmailField:
類似于CharField。在數(shù)據(jù)庫底層也是一個varchar類型。最大長度是254個字符。
FileField:
用來存儲文件的。這個請參考后面的文件上傳章節(jié)部分。
ImageField:
用來存儲圖片文件的。這個請參考后面的圖片上傳章節(jié)部分。
FloatField:
浮點類型。映射到數(shù)據(jù)庫中是float類型。
IntegerField:
整形。值的區(qū)間是-2147483648——2147483647。
BigIntegerField:
大整形。值的區(qū)間是-9223372036854775808——9223372036854775807。
PositiveIntegerField:
正整形。值的區(qū)間是0——2147483647。
SmallIntegerField:
小整形。值的區(qū)間是-32768——32767。
PositiveSmallIntegerField:
正小整形。值的區(qū)間是0——32767。
TextField:
大量的文本類型。映射到數(shù)據(jù)庫中是longtext類型。
UUIDField:
只能存儲uuid格式的字符串。uuid是一個32位的全球唯一的字符串,一般用來作為主鍵。
URLField:
類似于CharField,只不過只能用來存儲url格式的字符串。并且默認(rèn)的max_length是200。
Field的常用參數(shù):
null:
如果設(shè)置為True,Django將會在映射表的時候指定是否為空。默認(rèn)是為False。在使用字符串相關(guān)的Field(CharField/TextField)的時候,官方推薦盡量不要使用這個參數(shù),也就是保持默認(rèn)值False。因為Django在處理字符串相關(guān)的Field的時候,即使這個Field的null=False,如果你沒有給這個Field傳遞任何值,那么Django也會使用一個空的字符串""來作為默認(rèn)值存儲進(jìn)去。因此如果再使用null=True,Django會產(chǎn)生兩種空值的情形(NULL或者空字符串)。如果想要在表單驗證的時候允許這個字符串為空,那么建議使用blank=True。如果你的Field是BooleanField,那么對應(yīng)的可空的字段則為NullBooleanField。
blank:
標(biāo)識這個字段在表單驗證的時候是否可以為空。默認(rèn)是False。
這個和null是有區(qū)別的,null是一個純數(shù)據(jù)庫級別的。而blank是表單驗證級別的。
db_column:
這個字段在數(shù)據(jù)庫中的名字。如果沒有設(shè)置這個參數(shù),那么將會使用模型中屬性的名字。
default:
默認(rèn)值。可以為一個值,或者是一個函數(shù),但是不支持lambda表達(dá)式。并且不支持列表/字典/集合等可變的數(shù)據(jù)結(jié)構(gòu)。
primary_key:
是否為主鍵。默認(rèn)是False。
unique:
在表中這個字段的值是否唯一。一般是設(shè)置手機(jī)號碼/郵箱等。
更多Field參數(shù)請參考官方文檔:https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/
模型中Meta配置:
對于一些模型級別的配置。我們可以在模型中定義一個類,叫做Meta。然后在這個類中添加一些類屬性來控制模型的作用。比如我們想要在數(shù)據(jù)庫映射的時候使用自己指定的表名,而不是使用模型的名稱。那么我們可以在Meta類中添加一個db_table的屬性。示例代碼如下:
class Book(models.Model):name = models.CharField(max_length=20,null=False) desc = models.CharField(max_length=100,name='description',db_column="description1") class Meta: db_table = 'book_model' 以下將對Meta類中的一些常用配置進(jìn)行解釋。
db_table:
這個模型映射到數(shù)據(jù)庫中的表名。如果沒有指定這個參數(shù),那么在映射的時候?qū)褂媚P兔麃碜鳛槟J(rèn)的表名。
ordering:
設(shè)置在提取數(shù)據(jù)的排序方式。后面章節(jié)會講到如何查找數(shù)據(jù)。比如我想在查找數(shù)據(jù)的時候根據(jù)添加的時間排序,那么示例代碼如下:
class Book(models.Model):name = models.CharField(max_length=20,null=False) desc = models.CharField(max_length=100,name='description',db_column="description1") pub_date = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'book_model' ordering = ['pub_date'] 四、外鍵和表關(guān)系
外鍵:
在MySQL中,表有兩種引擎,一種是InnoDB,另外一種是myisam。如果使用的是InnoDB引擎,是支持外鍵約束的。外鍵的存在使得ORM框架在處理表關(guān)系的時候異常的強(qiáng)大。因此這里我們首先來介紹下外鍵在Django中的使用。
類定義為class ForeignKey(to,on_delete,**options)。第一個參數(shù)是引用的是哪個模型,第二個參數(shù)是在使用外鍵引用的模型數(shù)據(jù)被刪除了,這個字段該如何處理,比如有CASCADE、SET_NULL等。這里以一個實際案例來說明。比如有一個User和一個Article兩個模型。一個User可以發(fā)表多篇文章,一個Article只能有一個Author,并且通過外鍵進(jìn)行引用。那么相關(guān)的示例代碼如下:
class User(models.Model):username = models.CharField(max_length=20) password = models.CharField(max_length=100) class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("User",on_delete=models.CASCADE) 以上使用ForeignKey來定義模型之間的關(guān)系。即在article的實例中可以通過author屬性來操作對應(yīng)的User模型。這樣使用起來非常的方便。示例代碼如下:
article = Article(title='abc',content='123')
author = User(username='張三',password='111111')
article.author = author
article.save()# 修改article.author上的值 article.author.username = '李四' article.save() 為什么使用了ForeignKey后,就能通過author訪問到對應(yīng)的user對象呢。因此在底層,Django為Article表添加了一個屬性名_id的字段(比如author的字段名稱是author_id),這個字段是一個外鍵,記錄著對應(yīng)的作者的主鍵。以后通過article.author訪問的時候,實際上是先通過author_id找到對應(yīng)的數(shù)據(jù),然后再提取User表中的這條數(shù)據(jù),形成一個模型。
如果想要引用另外一個app的模型,那么應(yīng)該在傳遞to參數(shù)的時候,使用app.model_name進(jìn)行指定。以上例為例,如果User和Article不是在同一個app中,那么在引用的時候的示例代碼如下:
# User模型在user這個app中
class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) # Article模型在article這個app中 class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("user.User",on_delete=models.CASCADE) 如果模型的外鍵引用的是本身自己這個模型,那么to參數(shù)可以為'self',或者是這個模型的名字。在論壇開發(fā)中,一般評論都可以進(jìn)行二級評論,即可以針對另外一個評論進(jìn)行評論,那么在定義模型的時候就需要使用外鍵來引用自身。示例代碼如下:
class Comment(models.Model):content = models.TextField()origin_comment = models.ForeignKey('self',on_delete=models.CASCADE,null=True) # 或者 # origin_comment = models.ForeignKey('Comment',on_delete=models.CASCADE,null=True) 外鍵刪除操作:
如果一個模型使用了外鍵。那么在對方那個模型被刪掉后,該進(jìn)行什么樣的操作。可以通過on_delete來指定。可以指定的類型如下:
CASCADE:級聯(lián)操作。如果外鍵對應(yīng)的那條數(shù)據(jù)被刪除了,那么這條數(shù)據(jù)也會被刪除。PROTECT:受保護(hù)。即只要這條數(shù)據(jù)引用了外鍵的那條數(shù)據(jù),那么就不能刪除外鍵的那條數(shù)據(jù)。SET_NULL:設(shè)置為空。如果外鍵的那條數(shù)據(jù)被刪除了,那么在本條數(shù)據(jù)上就將這個字段設(shè)置為空。如果設(shè)置這個選項,前提是要指定這個字段可以為空。SET_DEFAULT:設(shè)置默認(rèn)值。如果外鍵的那條數(shù)據(jù)被刪除了,那么本條數(shù)據(jù)上就將這個字段設(shè)置為默認(rèn)值。如果設(shè)置這個選項,前提是要指定這個字段一個默認(rèn)值。SET():如果外鍵的那條數(shù)據(jù)被刪除了。那么將會獲取SET函數(shù)中的值來作為這個外鍵的值。SET函數(shù)可以接收一個可以調(diào)用的對象(比如函數(shù)或者方法),如果是可以調(diào)用的對象,那么會將這個對象調(diào)用后的結(jié)果作為值返回回去。DO_NOTHING:不采取任何行為。一切全看數(shù)據(jù)庫級別的約束。
以上這些選項只是Django級別的,數(shù)據(jù)級別依舊是RESTRICT!
表關(guān)系:
表之間的關(guān)系都是通過外鍵來進(jìn)行關(guān)聯(lián)的。而表之間的關(guān)系,無非就是三種關(guān)系:一對一、一對多(多對一)、多對多等。以下將討論一下三種關(guān)系的應(yīng)用場景及其實現(xiàn)方式。
一對多:
- 應(yīng)用場景:比如文章和作者之間的關(guān)系。一個文章只能由一個作者編寫,但是一個作者可以寫多篇文章。文章和作者之間的關(guān)系就是典型的多對一的關(guān)系。
-
實現(xiàn)方式:一對多或者多對一,都是通過
ForeignKey來實現(xiàn)的。還是以文章和作者的案例進(jìn)行講解。class User(models.Model):username = models.CharField(max_length=20) password = models.CharField(max_length=100) class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("User",on_delete=models.CASCADE)那么以后在給
Article對象指定author,就可以使用以下代碼來完成:article = Article(title='abc',content='123') author = User(username='zhiliao',password='111111') # 要先保存到數(shù)據(jù)庫中 author.save() article.author = author article.save()并且以后如果想要獲取某個用戶下所有的文章,可以通過
article_set來實現(xiàn)。示例代碼如下:user = User.objects.first() # 獲取第一個用戶寫的所有文章 articles = user.article_set.all() for article in articles:print(article)
一對一:
-
應(yīng)用場景:比如一個用戶表和一個用戶信息表。在實際網(wǎng)站中,可能需要保存用戶的許多信息,但是有些信息是不經(jīng)常用的。如果把所有信息都存放到一張表中可能會影響查詢效率,因此可以把用戶的一些不常用的信息存放到另外一張表中我們叫做
UserExtension。但是用戶表User和用戶信息表UserExtension就是典型的一對一了。 -
實現(xiàn)方式:
Django為一對一提供了一個專門的Field叫做OneToOneField來實現(xiàn)一對一操作。示例代碼如下:class User(models.Model):username = models.CharField(max_length=20) password = models.CharField(max_length=100) class UserExtension(models.Model): birthday = models.DateTimeField(null=True) school = models.CharField(blank=True,max_length=50) user = models.OneToOneField("User", on_delete=models.CASCADE)在
UserExtension模型上增加了一個一對一的關(guān)系映射。其實底層是在UserExtension這個表上增加了一個user_id,來和user表進(jìn)行關(guān)聯(lián),并且這個外鍵數(shù)據(jù)在表中必須是唯一的,來保證一對一。
多對多:
-
應(yīng)用場景:比如文章和標(biāo)簽的關(guān)系。一篇文章可以有多個標(biāo)簽,一個標(biāo)簽可以被多個文章所引用。因此標(biāo)簽和文章的關(guān)系是典型的多對多的關(guān)系。
-
實現(xiàn)方式:
Django為這種多對多的實現(xiàn)提供了專門的Field。叫做ManyToManyField。還是拿文章和標(biāo)簽為例進(jìn)行講解。示例代碼如下:class Article(models.Model):title = models.CharField(max_length=100) content = models.TextField() tags = models.ManyToManyField("Tag",related_name="articles") class Tag(models.Model): name = models.CharField(max_length=50)在數(shù)據(jù)庫層面,實際上
Django是為這種多對多的關(guān)系建立了一個中間表。這個中間表分別定義了兩個外鍵,引用到article和tag兩張表的主鍵。
related_name和related_query_name:
related_name:
還是以User和Article為例來進(jìn)行說明。如果一個article想要訪問對應(yīng)的作者,那么可以通過author來進(jìn)行訪問。但是如果有一個user對象,想要通過這個user對象獲取所有的文章,該如何做呢?這時候可以通過user.article_set來訪問,這個名字的規(guī)律是模型名字小寫_set。示例代碼如下:
user = User.objects.get(name='張三')
user.article_set.all()
如果不想使用模型名字小寫_set的方式,想要使用其他的名字,那么可以在定義模型的時候指定related_name。示例代碼如下:
class Article(models.Model):title = models.CharField(max_length=100) content = models.TextField() # 傳遞related_name參數(shù),以后在方向引用的時候使用articles進(jìn)行訪問 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles') 以后在方向引用的時候。使用articles可以訪問到這個作者的文章模型。示例代碼如下:
user = User.objects.get(name='張三')
user.articles.all()
如果不想使用反向引用,那么可以指定related_name='+'。示例代碼如下:
class Article(models.Model):title = models.CharField(max_length=100) content = models.TextField() # 傳遞related_name參數(shù),以后在方向引用的時候使用articles進(jìn)行訪問 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='+') 以后將不能通過user.article_set來訪問文章模型了。
related_query_name:
在查找數(shù)據(jù)的時候,可以使用filter進(jìn)行過濾。使用filter過濾的時候,不僅僅可以指定本模型上的某個屬性要滿足什么條件,還可以指定相關(guān)聯(lián)的模型滿足什么屬性。比如現(xiàn)在想要獲取寫過標(biāo)題為abc的所有用戶,那么可以這樣寫:
users = User.objects.filter(article__title='abc')
如果你設(shè)置了related_name為articles,因為反轉(zhuǎn)的過濾器的名字將使用related_name的名字,那么上例代碼將改成如下:
users = User.objects.filter(articles__title='abc')
可以通過related_query_name將查詢的反轉(zhuǎn)名字修改成其他的名字。比如article。示例代碼如下:
class Article(models.Model):title = models.CharField(max_length=100) content = models.TextField() # 傳遞related_name參數(shù),以后在方向引用的時候使用articles進(jìn)行訪問 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles',related_query_name='article') 那么在做反向過濾查找的時候就可以使用以下代碼:
users = User.objects.filter(article__title='abc') 五、模型的操作:
在ORM框架中,所有模型相關(guān)的操作,比如添加/刪除等。其實都是映射到數(shù)據(jù)庫中一條數(shù)據(jù)的操作。因此模型操作也就是數(shù)據(jù)庫表中數(shù)據(jù)的操作。
添加一個模型到數(shù)據(jù)庫中:
添加模型到數(shù)據(jù)庫中。首先需要創(chuàng)建一個模型。創(chuàng)建模型的方式很簡單,就跟創(chuàng)建普通的Python對象是一摸一樣的。在創(chuàng)建完模型之后,需要調(diào)用模型的save方法,這樣Django會自動的將這個模型轉(zhuǎn)換成sql語句,然后存儲到數(shù)據(jù)庫中。示例代碼如下:
class Book(models.Model):name = models.CharField(max_length=20,null=False) desc = models.CharField(max_length=100,name='description',db_column="description1") pub_date = models.DateTimeField(auto_now_add=True) book = Book(name='三國演義',desc='三國英雄!') book.save() 查找數(shù)據(jù):
查找數(shù)據(jù)都是通過模型下的objects對象來實現(xiàn)的。
查找所有數(shù)據(jù):
要查找Book這個模型對應(yīng)的表下的所有數(shù)據(jù)。那么示例代碼如下:
books = Book.objects.all()
以上將返回Book模型下的所有數(shù)據(jù)。
數(shù)據(jù)過濾:
在查找數(shù)據(jù)的時候,有時候需要對一些數(shù)據(jù)進(jìn)行過濾。那么這時候需要調(diào)用objects的filter方法。實例代碼如下:
books = Book.objects.filter(name='三國演義')
> [<Book:三國演義>]# 多個條件
books = Book.objects.filter(name='三國演義',desc='test')
調(diào)用filter,會將所有滿足條件的模型對象都返回。
獲取單個對象:
使用filter返回的是所有滿足條件的結(jié)果集。有時候如果只需要返回第一個滿足條件的對象。那么可以使用get方法。示例代碼如下:
book = Book.objects.get(name='三國演義')
> <Book:三國演義>
當(dāng)然,如果沒有找到滿足條件的對象,那么就會拋出一個異常。而filter在沒有找到滿足條件的數(shù)據(jù)的時候,是返回一個空的列表。
數(shù)據(jù)排序:
在之前的例子中,數(shù)據(jù)都是無序的。如果你想在查找數(shù)據(jù)的時候使用某個字段來進(jìn)行排序,那么可以使用order_by方法來實現(xiàn)。示例代碼如下:
books = Book.objects.order_by("pub_date")
以上代碼在提取所有書籍的數(shù)據(jù)的時候,將會使用pub_date從小到大進(jìn)行排序。如果想要進(jìn)行倒序排序,那么可以在pub_date前面加一個負(fù)號。實例代碼如下:
books = Book.objects.order_by("-pub_date")
修改數(shù)據(jù):
在查找到數(shù)據(jù)后,便可以進(jìn)行修改了。修改的方式非常簡單,只需要將查找出來的對象的某個屬性進(jìn)行修改,然后再調(diào)用這個對象的save方法便可以進(jìn)行修改。示例代碼如下:
from datetime import datetime
book = Book.objects.get(name='三國演義')
book.pub_date = datetime.now()
book.save()
刪除數(shù)據(jù):
在查找到數(shù)據(jù)后,便可以進(jìn)行刪除了。刪除數(shù)據(jù)非常簡單,只需要調(diào)用這個對象的delete方法即可。實例代碼如下:
book = Book.objects.get(name='三國演義')
book.delete() 六、查詢操作
查找是數(shù)據(jù)庫操作中一個非常重要的技術(shù)。查詢一般就是使用filter、exclude以及get三個方法來實現(xiàn)。我們可以在調(diào)用這些方法的時候傳遞不同的參數(shù)來實現(xiàn)查詢需求。在ORM層面,這些查詢條件都是使用field+__+condition的方式來使用的。以下將那些常用的查詢條件來一一解釋。
查詢條件
exact:
使用精確的=進(jìn)行查找。如果提供的是一個None,那么在SQL層面就是被解釋為NULL。示例代碼如下:
article = Article.objects.get(id__exact=14)
article = Article.objects.get(id__exact=None)
以上的兩個查找在翻譯為SQL語句為如下:
select ... from article where id=14; select ... from article where id IS NULL; iexact:
使用like進(jìn)行查找。示例代碼如下:
article = Article.objects.filter(title__iexact='hello world')
那么以上的查詢就等價于以下的SQL語句:
select ... from article where title like 'hello world'; 注意上面這個sql語句,因為在MySQL中,沒有一個叫做ilike的。所以exact和iexact的區(qū)別實際上就是LIKE和=的區(qū)別,在大部分collation=utf8_general_ci情況下都是一樣的(collation是用來對字符串比較的)。
contains:
大小寫敏感,判斷某個字段是否包含了某個數(shù)據(jù)。示例代碼如下:
articles = Article.objects.filter(title__contains='hello')
在翻譯成SQL語句為如下:
select ... where title like binary '%hello%'; 要注意的是,在使用contains的時候,翻譯成的sql語句左右兩邊是有百分號的,意味著使用的是模糊查詢。而exact翻譯成sql語句左右兩邊是沒有百分號的,意味著使用的是精確的查詢。
icontains:
大小寫不敏感的匹配查詢。示例代碼如下:
articles = Article.objects.filter(title__icontains='hello')
在翻譯成SQL語句為如下:
select ... where title like '%hello%';
in:
提取那些給定的field的值是否在給定的容器中。容器可以為list、tuple或者任何一個可以迭代的對象,包括QuerySet對象。示例代碼如下:
articles = Article.objects.filter(id__in=[1,2,3])
以上代碼在翻譯成SQL語句為如下:
select ... where id in (1,3,4) 當(dāng)然也可以傳遞一個QuerySet對象進(jìn)去。示例代碼如下:
inner_qs = Article.objects.filter(title__contains='hello')
categories = Category.objects.filter(article__in=inner_qs)
以上代碼的意思是獲取那些文章標(biāo)題包含hello的所有分類。
將翻譯成以下SQL語句,示例代碼如下:
select ...from category where article.id in (select id from article where title like '%hello%'); gt:
某個field的值要大于給定的值。示例代碼如下:
articles = Article.objects.filter(id__gt=4)
以上代碼的意思是將所有id大于4的文章全部都找出來。
將翻譯成以下SQL語句:
select ... where id > 4;
gte:
類似于gt,是大于等于。
lt:
類似于gt是小于。
lte:
類似于lt,是小于等于。
startswith:
判斷某個字段的值是否是以某個值開始的。大小寫敏感。示例代碼如下:
articles = Article.objects.filter(title__startswith='hello')
以上代碼的意思是提取所有標(biāo)題以hello字符串開頭的文章。
將翻譯成以下SQL語句:
select ... where title like 'hello%'
istartswith:
類似于startswith,但是大小寫是不敏感的。
endswith:
判斷某個字段的值是否以某個值結(jié)束。大小寫敏感。示例代碼如下:
articles = Article.objects.filter(title__endswith='world')
以上代碼的意思是提取所有標(biāo)題以world結(jié)尾的文章。
將翻譯成以下SQL語句:
select ... where title like '%world';
iendswith:
類似于endswith,只不過大小寫不敏感。
range:
判斷某個field的值是否在給定的區(qū)間中。示例代碼如下:
from django.utils.timezone import make_aware
from datetime import datetime
start_date = make_aware(datetime(year=2018,month=1,day=1)) end_date = make_aware(datetime(year=2018,month=3,day=29,hour=16)) articles = Article.objects.filter(pub_date__range=(start_date,end_date)) 以上代碼的意思是提取所有發(fā)布時間在2018/1/1到2018/12/12之間的文章。
將翻譯成以下的SQL語句:
select ... from article where pub_time between '2018-01-01' and '2018-12-12'。 需要注意的是,以上提取數(shù)據(jù),不會包含最后一個值。也就是不會包含2018/12/12的文章。
而且另外一個重點,因為我們在settings.py中指定了USE_TZ=True,并且設(shè)置了TIME_ZONE='Asia/Shanghai',因此我們在提取數(shù)據(jù)的時候要使用django.utils.timezone.make_aware先將datetime.datetime從navie時間轉(zhuǎn)換為aware時間。make_aware會將指定的時間轉(zhuǎn)換為TIME_ZONE中指定的時區(qū)的時間。
date:
針對某些date或者datetime類型的字段。可以指定date的范圍。并且這個時間過濾,還可以使用鏈?zhǔn)秸{(diào)用。示例代碼如下:
articles = Article.objects.filter(pub_date__date=date(2018,3,29))
以上代碼的意思是查找時間為2018/3/29這一天發(fā)表的所有文章。
將翻譯成以下的sql語句:
select ... WHERE DATE(CONVERT_TZ(`front_article`.`pub_date`, 'UTC', 'Asia/Shanghai')) = 2018-03-29 注意,因為默認(rèn)情況下MySQL的表中是沒有存儲時區(qū)相關(guān)的信息的。因此我們需要下載一些時區(qū)表的文件,然后添加到Mysql的配置路徑中。如果你用的是windows操作系統(tǒng)。那么在http://dev.mysql.com/downloads/timezones.html下載timezone_2018d_posix.zip - POSIX standard。然后將下載下來的所有文件拷貝到C:\ProgramData\MySQL\MySQL Server 5.7\Data\mysql中,如果提示文件名重復(fù),那么選擇覆蓋即可。
如果用的是linux或者mac系統(tǒng),那么在命令行中執(zhí)行以下命令:mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p,然后輸入密碼,從系統(tǒng)中加載時區(qū)文件更新到mysql中。
year:
根據(jù)年份進(jìn)行查找。示例代碼如下:
articles = Article.objects.filter(pub_date__year=2018)
articles = Article.objects.filter(pub_date__year__gte=2017)
以上的代碼在翻譯成SQL語句為如下:
select ... where pub_date between '2018-01-01' and '2018-12-31'; select ... where pub_date >= '2017-01-01'; month:
同year,根據(jù)月份進(jìn)行查找。
day:
同year,根據(jù)日期進(jìn)行查找。
week_day:
Django 1.11新增的查找方式。同year,根據(jù)星期幾進(jìn)行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。
time:
根據(jù)時間進(jìn)行查找。示例代碼如下:
articles = Article.objects.filter(pub_date__time=datetime.time(12,12,12));
以上的代碼是獲取每一天中12點12分12秒發(fā)表的所有文章。
更多的關(guān)于時間的過濾,請參考Django官方文檔:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#range。
isnull:
根據(jù)值是否為空進(jìn)行查找。示例代碼如下:
articles = Article.objects.filter(pub_date__isnull=False)
以上的代碼的意思是獲取所有發(fā)布日期不為空的文章。
將來翻譯成SQL語句如下:
select ... where pub_date is not null; regex和iregex:
大小寫敏感和大小寫不敏感的正則表達(dá)式。示例代碼如下:
articles = Article.objects.filter(title__regex=r'^hello')
以上代碼的意思是提取所有標(biāo)題以hello字符串開頭的文章。
將翻譯成以下的SQL語句:
select ... where title regexp binary '^hello';
iregex是大小寫不敏感的。
根據(jù)關(guān)聯(lián)的表進(jìn)行查詢:
假如現(xiàn)在有兩個ORM模型,一個是Article,一個是Category。代碼如下:
class Category(models.Model):"""文章分類表""" name = models.CharField(max_length=100) class Article(models.Model): """文章表""" title = models.CharField(max_length=100,null=True) category = models.ForeignKey("Category",on_delete=models.CASCADE) 比如想要獲取文章標(biāo)題中包含"hello"的所有的分類。那么可以通過以下代碼來實現(xiàn):
categories = Category.object.filter(article__title__contains("hello")) 聚合函數(shù):
如果你用原生SQL,則可以使用聚合函數(shù)來提取數(shù)據(jù)。比如提取某個商品銷售的數(shù)量,那么可以使用Count,如果想要知道商品銷售的平均價格,那么可以使用Avg。
聚合函數(shù)是通過aggregate方法來實現(xiàn)的。在講解這些聚合函數(shù)的用法的時候,都是基于以下的模型對象來實現(xiàn)的。
from django.db import modelsclass Author(models.Model): """作者模型""" name = models.CharField(max_length=100) age = models.IntegerField() email = models.EmailField() class Meta: db_table = 'author' class Publisher(models.Model): """出版社模型""" name = models.CharField(max_length=300) class Meta: db_table = 'publisher' class Book(models.Model): """圖書模型""" name = models.CharField(max_length=300) pages = models.IntegerField() price = models.FloatField() rating = models.FloatField() author = models.ForeignKey(Author,on_delete=models.CASCADE) publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE) class Meta: db_table = 'book' class BookOrder(models.Model): """圖書訂單模型""" book = models.ForeignKey("Book",on_delete=models.CASCADE) price = models.FloatField() class Meta: db_table = 'book_order' -
Avg:求平均值。比如想要獲取所有圖書的價格平均值。那么可以使用以下代碼實現(xiàn)。from django.db.models import Avgresult = Book.objects.aggregate(Avg('price'))print(result)以上的打印結(jié)果是:
{"price__avg":23.0}其中
price__avg的結(jié)構(gòu)是根據(jù)field__avg規(guī)則構(gòu)成的。如果想要修改默認(rèn)的名字,那么可以將Avg賦值給一個關(guān)鍵字參數(shù)。示例代碼如下:from django.db.models import Avgresult = Book.objects.aggregate(my_avg=Avg('price'))print(result)那么以上的結(jié)果打印為:
{"my_avg":23} -
Count:獲取指定的對象的個數(shù)。示例代碼如下:from django.db.models import Countresult = Book.objects.aggregate(book_num=Count('id'))以上的
result將返回Book表中總共有多少本圖書。Count類中,還有另外一個參數(shù)叫做distinct,默認(rèn)是等于False,如果是等于True,那么將去掉那些重復(fù)的值。比如要獲取作者表中所有的不重復(fù)的郵箱總共有多少個,那么可以通過以下代碼來實現(xiàn):from djang.db.models import Countresult = Author.objects.aggregate(count=Count('email',distinct=True)) -
Max和Min:獲取指定對象的最大值和最小值。比如想要獲取Author表中,最大的年齡和最小的年齡分別是多少。那么可以通過以下代碼來實現(xiàn):from django.db.models import Max,Minresult = Author.objects.aggregate(Max('age'),Min('age'))如果最大的年齡是88,最小的年齡是18。那么以上的result將為:
{"age__max":88,"age__min":18} -
Sum:求指定對象的總和。比如要求圖書的銷售總額。那么可以使用以下代碼實現(xiàn):from djang.db.models import Sumresult = Book.objects.annotate(total=Sum("bookstore__price")).values("name","total")以上的代碼
annotate的意思是給Book表在查詢的時候添加一個字段叫做total,這個字段的數(shù)據(jù)來源是從BookStore模型的price的總和而來。values方法是只提取name和total兩個字段的值。
更多的聚合函數(shù)請參考官方文檔:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions
aggregate和annotate的區(qū)別:
-
aggregate:返回使用聚合函數(shù)后的字段和值。 -
annotate:在原來模型字段的基礎(chǔ)之上添加一個使用了聚合函數(shù)的字段,并且在使用聚合函數(shù)的時候,會使用當(dāng)前這個模型的主鍵進(jìn)行分組(group by)。
比如以上Sum的例子,如果使用的是annotate,那么將在每條圖書的數(shù)據(jù)上都添加一個字段叫做total,計算這本書的銷售總額。
而如果使用的是aggregate,那么將求所有圖書的銷售總額。
F表達(dá)式和Q表達(dá)式:
F表達(dá)式:
F表達(dá)式是用來優(yōu)化ORM操作數(shù)據(jù)庫的。比如我們要將公司所有員工的薪水都增加1000元,如果按照正常的流程,應(yīng)該是先從數(shù)據(jù)庫中提取所有的員工工資到Python內(nèi)存中,然后使用Python代碼在員工工資的基礎(chǔ)之上增加1000元,最后再保存到數(shù)據(jù)庫中。這里面涉及的流程就是,首先從數(shù)據(jù)庫中提取數(shù)據(jù)到Python內(nèi)存中,然后在Python內(nèi)存中做完運算,之后再保存到數(shù)據(jù)庫中。示例代碼如下:
employees = Employee.objects.all()
for employee in employees:employee.salary += 1000employee.save()
而我們的F表達(dá)式就可以優(yōu)化這個流程,他可以不需要先把數(shù)據(jù)從數(shù)據(jù)庫中提取出來,計算完成后再保存回去,他可以直接執(zhí)行SQL語句,就將員工的工資增加1000元。示例代碼如下:
from djang.db.models import F
Employee.object.update(salary=F("salary")+1000)
F表達(dá)式并不會馬上從數(shù)據(jù)庫中獲取數(shù)據(jù),而是在生成SQL語句的時候,動態(tài)的獲取傳給F表達(dá)式的值。
比如如果想要獲取作者中,name和email相同的作者數(shù)據(jù)。如果不使用F表達(dá)式,那么需要使用以下代碼來完成:
authors = Author.objects.all()for author in authors:if author.name == author.email:print(author)
如果使用F表達(dá)式,那么一行代碼就可以搞定。示例代碼如下:
from django.db.models import Fauthors = Author.objects.filter(name=F("email"))
Q表達(dá)式:
如果想要實現(xiàn)所有價格高于100元,并且評分達(dá)到9.0以上評分的圖書。那么可以通過以下代碼來實現(xiàn):
books = Book.objects.filter(price__gte=100,rating__gte=9)
以上這個案例是一個并集查詢,可以簡單的通過傳遞多個條件進(jìn)去來實現(xiàn)。
但是如果想要實現(xiàn)一些復(fù)雜的查詢語句,比如要查詢所有價格低于10元,或者是評分低于9分的圖書。那就沒有辦法通過傳遞多個條件進(jìn)去實現(xiàn)了。這時候就需要使用Q表達(dá)式來實現(xiàn)了。示例代碼如下:
from django.db.models import Q
books = Book.objects.filter(Q(price__lte=10) | Q(rating__lte=9))
以上是進(jìn)行或運算,當(dāng)然還可以進(jìn)行其他的運算,比如有&和~(非)等。一些用Q表達(dá)式的例子如下:
from django.db.models import Q
# 獲取id等于3的圖書
books = Book.objects.filter(Q(id=3))
# 獲取id等于3,或者名字中包含文字"記"的圖書 books = Book.objects.filter(Q(id=3)|Q(name__contains("記"))) # 獲取價格大于100,并且書名中包含"記"的圖書 books = Book.objects.filter(Q(price__gte=100)&Q(name__contains("記"))) # 獲取書名包含“記”,但是id不等于3的圖書 books = Book.objects.filter(Q(name__contains='記') & ~Q(id=3)) 七、QuerySet API:
我們通常做查詢操作的時候,都是通過模型名字.objects的方式進(jìn)行操作。其實模型名字.objects是一個django.db.models.manager.Manager對象,而Manager這個類是一個“空殼”的類,他本身是沒有任何的屬性和方法的。他的方法全部都是通過Python動態(tài)添加的方式,從QuerySet類中拷貝過來的。示例圖如下:
所以我們?nèi)绻胍獙W(xué)習(xí)ORM模型的查找操作,必須首先要學(xué)會QuerySet上的一些API的使用。
返回新的QuerySet的方法:
在使用QuerySet進(jìn)行查找操作的時候,可以提供多種操作。比如過濾完后還要根據(jù)某個字段進(jìn)行排序,那么這一系列的操作我們可以通過一個非常流暢的鏈?zhǔn)秸{(diào)用的方式進(jìn)行。比如要從文章表中獲取標(biāo)題為123,并且提取后要將結(jié)果根據(jù)發(fā)布的時間進(jìn)行排序,那么可以使用以下方式來完成:
articles = Article.objects.filter(title='123').order_by('create_time')
可以看到order_by方法是直接在filter執(zhí)行后調(diào)用的。這說明filter返回的對象是一個擁有order_by方法的對象。而這個對象正是一個新的QuerySet對象。因此可以使用order_by方法。
那么以下將介紹在那些會返回新的QuerySet對象的方法。
-
filter:將滿足條件的數(shù)據(jù)提取出來,返回一個新的QuerySet。具體的filter可以提供什么條件查詢。請見查詢操作章節(jié)。 -
exclude:排除滿足條件的數(shù)據(jù),返回一個新的QuerySet。示例代碼如下:Article.objects.exclude(title__contains='hello')以上代碼的意思是提取那些標(biāo)題不包含
hello的圖書。 -
annotate:給QuerySet中的每個對象都添加一個使用查詢表達(dá)式(聚合函數(shù)、F表達(dá)式、Q表達(dá)式、Func表達(dá)式等)的新字段。示例代碼如下:articles = Article.objects.annotate(author_name=F("author__name"))以上代碼將在每個對象中都添加一個
author__name的字段,用來顯示這個文章的作者的年齡。 -
order_by:指定將查詢的結(jié)果根據(jù)某個字段進(jìn)行排序。如果要倒敘排序,那么可以在這個字段的前面加一個負(fù)號。示例代碼如下:# 根據(jù)創(chuàng)建的時間正序排序articles = Article.objects.order_by("create_time")# 根據(jù)創(chuàng)建的時間倒序排序articles = Article.objects.order_by("-create_time")# 根據(jù)作者的名字進(jìn)行排序 articles = Article.objects.order_by("author__name") # 首先根據(jù)創(chuàng)建的時間進(jìn)行排序,如果時間相同,則根據(jù)作者的名字進(jìn)行排序 articles = Article.objects.order_by("create_time",'author__name')一定要注意的一點是,多個
order_by,會把前面排序的規(guī)則給打亂,而使用后面的排序方式。比如以下代碼:articles = Article.objects.order_by("create_time").order_by("author__name")他會根據(jù)作者的名字進(jìn)行排序,而不是使用文章的創(chuàng)建時間。
-
values:用來指定在提取數(shù)據(jù)出來,需要提取哪些字段。默認(rèn)情況下會把表中所有的字段全部都提取出來,可以使用values來進(jìn)行指定,并且使用了values方法后,提取出的QuerySet中的數(shù)據(jù)類型不是模型,而是在values方法中指定的字段和值形成的字典:articles = Article.objects.values("title",'content')for article in articles:print(article)以上打印出來的
article是類似于{"title":"abc","content":"xxx"}的形式。
如果在values中沒有傳遞任何參數(shù),那么將會返回這個惡模型中所有的屬性。 -
values_list:類似于values。只不過返回的QuerySet中,存儲的不是字典,而是元組。示例代碼如下:articles = Article.objects.values_list("id","title")print(articles)那么在打印
articles后,結(jié)果為<QuerySet [(1,'abc'),(2,'xxx'),...]>等。
如果在values_list中只有一個字段。那么你可以傳遞flat=True來將結(jié)果扁平化。示例代碼如下:articles1 = Article.objects.values_list("title")>> <QuerySet [("abc",),("xxx",),...]>articles2 = Article.objects.values_list("title",flat=True) >> <QuerySet ["abc",'xxx',...]> -
all:獲取這個ORM模型的QuerySet對象。 -
select_related:在提取某個模型的數(shù)據(jù)的同時,也提前將相關(guān)聯(lián)的數(shù)據(jù)提取出來。比如提取文章數(shù)據(jù),可以使用select_related將author信息提取出來,以后再次使用article.author的時候就不需要再次去訪問數(shù)據(jù)庫了。可以減少數(shù)據(jù)庫查詢的次數(shù)。示例代碼如下:article = Article.objects.get(pk=1)>> article.author # 重新執(zhí)行一次查詢語句article = Article.objects.select_related("author").get(pk=2)>> article.author # 不需要重新執(zhí)行查詢語句了select_related只能用在一對多或者一對一中,不能用在多對多或者多對一中。比如可以提前獲取文章的作者,但是不能通過作者獲取這個作者的文章,或者是通過某篇文章獲取這個文章所有的標(biāo)簽。 -
prefetch_related:這個方法和select_related非常的類似,就是在訪問多個表中的數(shù)據(jù)的時候,減少查詢的次數(shù)。這個方法是為了解決多對一和多對多的關(guān)系的查詢問題。比如要獲取標(biāo)題中帶有hello字符串的文章以及他的所有標(biāo)簽,示例代碼如下:from django.db import connectionarticles = Article.objects.prefetch_related("tag_set").filter(title__contains='hello')print(articles.query) # 通過這條命令查看在底層的SQL語句 for article in articles: print("title:",article.title) print(article.tag_set.all()) # 通過以下代碼可以看出以上代碼執(zhí)行的sql語句 for sql in connection.queries: print(sql)但是如果在使用
article.tag_set的時候,如果又創(chuàng)建了一個新的QuerySet那么會把之前的SQL優(yōu)化給破壞掉。比如以下代碼:tags = Tag.obejcts.prefetch_related("articles")for tag in tags:articles = tag.articles.filter(title__contains='hello') #因為filter方法會重新生成一個QuerySet,因此會破壞掉之前的sql優(yōu)化 # 通過以下代碼,我們可以看到在使用了filter的,他的sql查詢會更多,而沒有使用filter的,只有兩次sql查詢 for sql in connection.queries: print(sql)那如果確實是想要在查詢的時候指定過濾條件該如何做呢,這時候我們可以使用
django.db.models.Prefetch來實現(xiàn),Prefetch這個可以提前定義好queryset。示例代碼如下:tags = Tag.objects.prefetch_related(Prefetch("articles",queryset=Article.objects.filter(title__contains='hello'))).all()for tag in tags:articles = tag.articles.all()for article in articles: print(article) for sql in connection.queries: print('='*30) print(sql)因為使用了
Prefetch,即使在查詢文章的時候使用了filter,也只會發(fā)生兩次查詢操作。 -
defer:在一些表中,可能存在很多的字段,但是一些字段的數(shù)據(jù)量可能是比較龐大的,而此時你又不需要,比如我們在獲取文章列表的時候,文章的內(nèi)容我們是不需要的,因此這時候我們就可以使用defer來過濾掉一些字段。這個字段跟values有點類似,只不過defer返回的不是字典,而是模型。示例代碼如下:articles = list(Article.objects.defer("title")) for sql in connection.queries:print('='*30) print(sql)在看以上代碼的
sql語句,你就可以看到,查找文章的字段,除了title,其他字段都查找出來了。當(dāng)然,你也可以使用article.title來獲取這個文章的標(biāo)題,但是會重新執(zhí)行一個查詢的語句。示例代碼如下:articles = list(Article.objects.defer("title")) for article in articles:# 因為在上面提取的時候過濾了title# 這個地方重新獲取title,將重新向數(shù)據(jù)庫中進(jìn)行一次查找操作 print(article.title) for sql in connection.queries: print('='*30) print(sql)defer雖然能過濾字段,但是有些字段是不能過濾的,比如id,即使你過濾了,也會提取出來。 -
only:跟defer類似,只不過defer是過濾掉指定的字段,而only是只提取指定的字段。 -
get:獲取滿足條件的數(shù)據(jù)。這個函數(shù)只能返回一條數(shù)據(jù),并且如果給的條件有多條數(shù)據(jù),那么這個方法會拋出MultipleObjectsReturned錯誤,如果給的條件沒有任何數(shù)據(jù),那么就會拋出DoesNotExit錯誤。所以這個方法在獲取數(shù)據(jù)的只能,只能有且只有一條。 -
create:創(chuàng)建一條數(shù)據(jù),并且保存到數(shù)據(jù)庫中。這個方法相當(dāng)于先用指定的模型創(chuàng)建一個對象,然后再調(diào)用這個對象的save方法。示例代碼如下:article = Article(title='abc') article.save()# 下面這行代碼相當(dāng)于以上兩行代碼 article = Article.objects.create(title='abc') -
get_or_create:根據(jù)某個條件進(jìn)行查找,如果找到了那么就返回這條數(shù)據(jù),如果沒有查找到,那么就創(chuàng)建一個。示例代碼如下:obj,created= Category.objects.get_or_create(title='默認(rèn)分類')如果有標(biāo)題等于
默認(rèn)分類的分類,那么就會查找出來,如果沒有,則會創(chuàng)建并且存儲到數(shù)據(jù)庫中。
這個方法的返回值是一個元組,元組的第一個參數(shù)obj是這個對象,第二個參數(shù)created代表是否創(chuàng)建的。 -
bulk_create:一次性創(chuàng)建多個數(shù)據(jù)。示例代碼如下:Tag.objects.bulk_create([Tag(name='111'),Tag(name='222'), ]) -
count:獲取提取的數(shù)據(jù)的個數(shù)。如果想要知道總共有多少條數(shù)據(jù),那么建議使用count,而不是使用len(articles)這種。因為count在底層是使用select count(*)來實現(xiàn)的,這種方式比使用len函數(shù)更加的高效。 -
first和last:返回QuerySet中的第一條和最后一條數(shù)據(jù)。 -
aggregate:使用聚合函數(shù)。 -
exists:判斷某個條件的數(shù)據(jù)是否存在。如果要判斷某個條件的元素是否存在,那么建議使用exists,這比使用count或者直接判斷QuerySet更有效得多。示例代碼如下:if Article.objects.filter(title__contains='hello').exists():print(True) 比使用count更高效: if Article.objects.filter(title__contains='hello').count() > 0: print(True) 也比直接判斷QuerySet更高效: if Article.objects.filter(title__contains='hello'): print(True) -
distinct:去除掉那些重復(fù)的數(shù)據(jù)。這個方法如果底層數(shù)據(jù)庫用的是MySQL,那么不能傳遞任何的參數(shù)。比如想要提取所有銷售的價格超過80元的圖書,并且刪掉那些重復(fù)的,那么可以使用distinct來幫我們實現(xiàn),示例代碼如下:books = Book.objects.filter(bookorder__price__gte=80).distinct()需要注意的是,如果在
distinct之前使用了order_by,那么因為order_by會提取order_by中指定的字段,因此再使用distinct就會根據(jù)多個字段來進(jìn)行唯一化,所以就不會把那些重復(fù)的數(shù)據(jù)刪掉。示例代碼如下:orders = BookOrder.objects.order_by("create_time").values("book_id").distinct()那么以上代碼因為使用了
order_by,即使使用了distinct,也會把重復(fù)的book_id提取出來。 -
update:執(zhí)行更新操作,在SQL底層走的也是update命令。比如要將所有category為空的article的article字段都更新為默認(rèn)的分類。示例代碼如下:Article.objects.filter(category__isnull=True).update(category_id=3)注意這個方法走的是更新的邏輯。所以更新完成后保存到數(shù)據(jù)庫中不會執(zhí)行
save方法,因此不會更新auto_now設(shè)置的字段。 -
delete:刪除所有滿足條件的數(shù)據(jù)。刪除數(shù)據(jù)的時候,要注意on_delete指定的處理方式。 -
切片操作:有時候我們查找數(shù)據(jù),有可能只需要其中的一部分。那么這時候可以使用切片操作來幫我們完成。
QuerySet使用切片操作就跟列表使用切片操作是一樣的。示例代碼如下:books = Book.objects.all()[1:3] for book in books:print(book)切片操作并不是把所有數(shù)據(jù)從數(shù)據(jù)庫中提取出來再做切片操作。而是在數(shù)據(jù)庫層面使用
LIMIE和OFFSET來幫我們完成。所以如果只需要取其中一部分的數(shù)據(jù)的時候,建議大家使用切片操作。
什么時候Django會將QuerySet轉(zhuǎn)換為SQL去執(zhí)行:
生成一個QuerySet對象并不會馬上轉(zhuǎn)換為SQL語句去執(zhí)行。
比如我們獲取Book表下所有的圖書:
books = Book.objects.all()
print(connection.queries)
我們可以看到在打印connection.quries的時候打印的是一個空的列表。說明上面的QuerySet并沒有真正的執(zhí)行。
在以下情況下QuerySet會被轉(zhuǎn)換為SQL語句執(zhí)行:
-
迭代:在遍歷
QuerySet對象的時候,會首先先執(zhí)行這個SQL語句,然后再把這個結(jié)果返回進(jìn)行迭代。比如以下代碼就會轉(zhuǎn)換為SQL語句:for book in Book.objects.all():print(book) -
使用步長做切片操作:
QuerySet可以類似于列表一樣做切片操作。做切片操作本身不會執(zhí)行SQL語句,但是如果如果在做切片操作的時候提供了步長,那么就會立馬執(zhí)行SQL語句。需要注意的是,做切片后不能再執(zhí)行filter方法,否則會報錯。 -
調(diào)用
len函數(shù):調(diào)用len函數(shù)用來獲取QuerySet中總共有多少條數(shù)據(jù)也會執(zhí)行SQL語句。 -
調(diào)用
list函數(shù):調(diào)用list函數(shù)用來將一個QuerySet對象轉(zhuǎn)換為list對象也會立馬執(zhí)行SQL語句。 -
判斷:如果對某個
QuerySet進(jìn)行判斷,也會立馬執(zhí)行SQL語句。
八、ORM模型遷移
遷移命令:
-
makemigrations:將模型生成遷移腳本。模型所在的
app,必須放在settings.py中的INSTALLED_APPS中。這個命令有以下幾個常用選項:- app_label:后面可以跟一個或者多個
app,那么就只會針對這幾個app生成遷移腳本。如果沒有任何的app_label,那么會檢查INSTALLED_APPS中所有的app下的模型,針對每一個app都生成響應(yīng)的遷移腳本。 - --name:給這個遷移腳本指定一個名字。
- --empty:生成一個空的遷移腳本。如果你想寫自己的遷移腳本,可以使用這個命令來實現(xiàn)一個空的文件,然后自己再在文件中寫遷移腳本。
- app_label:后面可以跟一個或者多個
-
migrate:將新生成的遷移腳本。映射到數(shù)據(jù)庫中。創(chuàng)建新的表或者修改表的結(jié)構(gòu)。以下一些常用的選項:
- app_label:將某個
app下的遷移腳本映射到數(shù)據(jù)庫中。如果沒有指定,那么會將所有在INSTALLED_APPS中的app下的模型都映射到數(shù)據(jù)庫中。 - app_label migrationname:將某個
app下指定名字的migration文件映射到數(shù)據(jù)庫中。 - --fake:可以將指定的遷移腳本名字添加到數(shù)據(jù)庫中。但是并不會把遷移腳本轉(zhuǎn)換為SQL語句,修改數(shù)據(jù)庫中的表。
- --fake-initial:將第一次生成的遷移文件版本號記錄在數(shù)據(jù)庫中。但并不會真正的執(zhí)行遷移腳本。
- app_label:將某個
-
showmigrations:查看某個app下的遷移文件。如果后面沒有app,那么將查看
INSTALLED_APPS中所有的遷移文件。 -
sqlmigrate:查看某個遷移文件在映射到數(shù)據(jù)庫中的時候,轉(zhuǎn)換的
SQL語句。
migrations中的遷移版本和數(shù)據(jù)庫中的遷移版本對不上怎么辦?
- 找到哪里不一致,然后使用
python manage.py --fake [版本名字],將這個版本標(biāo)記為已經(jīng)映射。 - 刪除指定
app下migrations和數(shù)據(jù)庫表django_migrations中和這個app相關(guān)的版本號,然后將模型中的字段和數(shù)據(jù)庫中的字段保持一致,再使用命令python manage.py makemigrations重新生成一個初始化的遷移腳本,之后再使用命令python manage.py makemigrations --fake-initial來將這個初始化的遷移腳本標(biāo)記為已經(jīng)映射。以后再修改就沒有問題了。
更多關(guān)于遷移腳本的。請查看官方文檔:https://docs.djangoproject.com/en/2.0/topics/migrations/
根據(jù)已有的表自動生成模型:
在實際開發(fā)中,有些時候可能數(shù)據(jù)庫已經(jīng)存在了。如果我們用Django來開發(fā)一個網(wǎng)站,讀取的是之前已經(jīng)存在的數(shù)據(jù)庫中的數(shù)據(jù)。那么該如何將模型與數(shù)據(jù)庫中的表映射呢?根據(jù)舊的數(shù)據(jù)庫生成對應(yīng)的ORM模型,需要以下幾個步驟:
-
Django給我們提供了一個inspectdb的命令,可以非常方便的將已經(jīng)存在的表,自動的生成模型。想要使用inspectdb自動將表生成模型。首先需要在settings.py中配置好數(shù)據(jù)庫相關(guān)信息。不然就找不到數(shù)據(jù)庫。示例代碼如下:DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': "migrations_demo", 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'root' } }比如有以下表:
-
article表:
-
tag表:
-
article_tag表:
-
front_user表:
那么通過
python manage.py inspectdb,就會將表轉(zhuǎn)換為模型后的代碼,顯示在終端:from django.db import modelsclass ArticleArticle(models.Model): title = models.CharField(max_length=100) content = models.TextField(blank=True, null=True) create_time = models.DateTimeField(blank=True, null=True) author = models.ForeignKey('FrontUserFrontuser', models.DO_NOTHING, blank=True, null=True) class Meta: managed = False db_table = 'article_article' class ArticleArticleTags(models.Model): article = models.ForeignKey(ArticleArticle, models.DO_NOTHING) tag = models.ForeignKey('ArticleTag', models.DO_NOTHING) class Meta: managed = False db_table = 'article_article_tags' unique_together = (('article', 'tag'),) class ArticleTag(models.Model): name = models.CharField(max_length=100) class Meta: managed = False db_table = 'article_tag' class FrontUserFrontuser(models.Model): username = models.CharField(max_length=100) telephone = models.CharField(max_length=11) class Meta: managed = False db_table = 'front_user_frontuser'以上代碼只是顯示在終端。如果想要保存到文件中。那么可以使用
>重定向輸出到指定的文件。比如讓他輸出到models.py文件中。示例命令如下:python manage.py inspectdb > models.py以上的命令,只能在終端執(zhí)行,不能在
pycharm->Tools->Run manage.py Task...中使用。如果只是想要轉(zhuǎn)換一個表為模型。那么可以指定表的名字。示例命令如下:
python manage.py inspectdb article_article > models.py
-
-
修正模型:新生成的
ORM模型有些地方可能不太適合使用。比如模型的名字,表之間的關(guān)系等等。那么以下選項還需要重新配置一下:- 模型名:自動生成的模型,是根據(jù)表的名字生成的,可能不是你想要的。這時候模型的名字你可以改成任何你想要的。
- 模型所屬app:根據(jù)自己的需要,將相應(yīng)的模型放在對應(yīng)的app中。放在同一個app中也是沒有任何問題的。只是不方便管理。
- 模型外鍵引用:將所有使用
ForeignKey的地方,模型引用都改成字符串。這樣不會產(chǎn)生模型順序的問題。另外,如果引用的模型已經(jīng)移動到其他的app中了,那么還要加上這個app的前綴。 - 讓Django管理模型:將
Meta下的managed=False刪掉,如果保留這個,那么以后這個模型有任何的修改,使用migrate都不會映射到數(shù)據(jù)庫中。 -
當(dāng)有多對多的時候,應(yīng)該也要修正模型。將中間表注視了,然后使用
ManyToManyField來實現(xiàn)多對多。并且,使用ManyToManyField生成的中間表的名字可能和數(shù)據(jù)庫中那個中間表的名字不一致,這時候肯定就不能正常連接了。那么可以通過db_table來指定中間表的名字。示例代碼如下:class Article(models.Model):title = models.CharField(max_length=100, blank=True, null=True) content = models.TextField(blank=True, null=True) author = models.ForeignKey('front.User', models.SET_NULL, blank=True, null=True) # 使用ManyToManyField模型到表,生成的中間表的規(guī)則是:article_tags # 但現(xiàn)在已經(jīng)存在的表的名字叫做:article_tag # 可以使用db_table,指定中間表的名字 tags = models.ManyToManyField("Tag",db_table='article_tag') class Meta: db_table = 'article' - 表名:切記不要修改表的名字。不然映射到數(shù)據(jù)庫中,會發(fā)生找不到對應(yīng)表的錯誤。
-
執(zhí)行命令
python manage.py makemigrations生成初始化的遷移腳本。方便后面通過ORM來管理表。這時候還需要執(zhí)行命令python manage.py migrate --fake-initial,因為如果不使用--fake-initial,那么會將遷移腳本會映射到數(shù)據(jù)庫中。這時候遷移腳本會新創(chuàng)建表,而這個表之前是已經(jīng)存在了的,所以肯定會報錯。此時我們只要將這個0001-initial的狀態(tài)修改為已經(jīng)映射,而不真正執(zhí)行映射,下次再migrate的時候,就會忽略他。 -
將
Django的核心表映射到數(shù)據(jù)庫中:Django中還有一些核心的表也是需要創(chuàng)建的。不然有些功能是用不了的。比如auth相關(guān)表。如果這個數(shù)據(jù)庫之前就是使用Django開發(fā)的,那么這些表就已經(jīng)存在了。可以不用管了。如果之前這個數(shù)據(jù)庫不是使用Django開發(fā)的,那么應(yīng)該使用migrate命令將Django中的核心模型映射到數(shù)據(jù)庫中。
轉(zhuǎn)載于:https://www.cnblogs.com/fisherbook/p/11068491.html
總結(jié)
以上是生活随笔為你收集整理的Django 数据库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在做项目中遇到的JS问题
- 下一篇: JS数组去重精简版