Object Relational Mapping(ORM)
ORM概念
對象關系映射(Object Relational Mapping,簡稱ORM)模式是一種為了解決面向對象與關系資料庫存在的互不比對的現象的技術。
簡單的說,ORM是通過使用描述對象和資料庫之間映射的中繼資料,将程式中的對象自動持久化到關系資料庫中。
ORM在業務邏輯層和資料庫層之間充當了橋梁的作用。
ORM由來
讓我們從O/R開始。字母O起源于"對象"(Object),而R則來自于"關系"(Relational)。
幾乎所有的軟體開發過程中都會涉及到對象和關系資料庫。在使用者層面和業務邏輯層面,我們是面向對象的。當對象的資訊發生變化的時候,我們就需要把對象的資訊儲存在關系資料庫中。
按照之前的方式來進行開發就會出現程式員會在自己的業務邏輯代碼中夾雜很多SQL語句用來增加、讀取、修改、删除相關資料,而這些代碼通常都是重複的。
ORM的優勢
ORM解決的主要問題是對象和關系的映射。它通常把一個類和一個表一一對應,類的每個執行個體對應表中的一條記錄,類的每個屬性對應表中的每個字段。
ORM提供了對資料庫的映射,不用直接編寫SQL代碼,隻需像操作對象一樣從資料庫操作資料。
讓軟體開發人員專注于業務邏輯的處理,提高了開發效率。
ORM的劣勢
ORM的缺點是會在一定程度上犧牲程式的執行效率。
ORM用多了SQL語句就不會寫了,關系資料庫相關技能退化...
ORM總結
ORM隻是一種工具,工具确實能解決一些重複,簡單的勞動。這是不可否認的。
但我們不能指望某個工具能一勞永逸地解決所有問題,一些特殊問題還是需要特殊處理的。
但是在整個軟體開發過程中需要特殊處理的情況應該都是很少的,否則所謂的工具也就失去了它存在的意義。
Django中的ORM
Django項目使用MySQL資料庫
1. 在Django項目的settings.py檔案中,配置資料庫連接配接資訊:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "你的資料庫名稱", # 需要自己手動建立資料庫
"USER": "資料庫使用者名",
"PASSWORD": "資料庫密碼",
"HOST": "資料庫IP",
"POST": 3306
}
}
2. 在Django項目的__init__.py檔案中寫如下代碼,告訴Django使用pymysql子產品連接配接MySQL資料庫:
import pymysql
pymysql.install_as_MySQLdb()
Model
在Django中model是你資料的單一、明确的資訊來源。它包含了你存儲的資料的重要字段和行為。通常,一個模型(model)映射到一個資料庫表,
基本情況:
- 每個模型都是一個Python類,它是django.db.models.Model的子類。
- 模型的每個屬性都代表一個資料庫字段。
- 綜上所述,Django為您提供了一個自動生成的資料庫通路API,詳詢官方文檔連結。
快速入門
下面這個例子定義了一個 Person 模型,包含 first_name 和 last_name。
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name 和 last_name 是模型的字段。每個字段被指定為一個類屬性,每個屬性映射到一個資料庫列。
上面的 Person 模型将會像這樣建立一個資料庫表:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
一些說明:
- 表myapp_person的名稱是自動生成的,如果你要自定義表名,需要在model的Meta類中指定 db_table 參數,強烈建議使用小寫表名,特别是使用MySQL作為後端資料庫時。
- id字段是自動添加的,如果你想要指定自定義主鍵,隻需在其中一個字段中指定 primary_key=True 即可。如果Django發現你已經明确地設定了Field.primary_key,它将不會添加自動ID列。
- 本示例中的CREATE TABLE SQL使用PostgreSQL文法進行格式化,但值得注意的是,Django會根據配置檔案中指定的資料庫後端類型來生成相應的SQL語句。
- Django支援MySQL5.5及更高版本。
Django ORM 常用字段和參數
常用字段
AutoField
int自增列,必須填入參數 primary_key=True。當model中如果沒有自增列,則自動會建立一個列名為id的列。
IntegerField
一個整數類型,範圍在 -2147483648 to 2147483647。
CharField
字元類型,必須提供max_length參數, max_length表示字元長度。
DateField
日期字段,日期格式 YYYY-MM-DD,相當于Python中的datetime.date()執行個體。
DateTimeField
日期時間字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相當于Python中的datetime.datetime()執行個體。
字段合集(争取記憶)
字段合集
自定義字段(了解為主)
class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'
自定義char類型字段:
class FixedCharField(models.Field):
"""
自定義的char類型的字段類
"""
def __init__(self, max_length, *args, **kwargs):
super().__init__(max_length=max_length, *args, **kwargs)
self.length = max_length
def db_type(self, connection):
"""
限定生成資料庫表的字段類型為char,長度為length指定的值
"""
return 'char(%s)' % self.length
class Class(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=25)
# 使用上面自定義的char類型的字段
cname = FixedCharField(max_length=25)
建立的表結構:
附ORM字段與資料庫實際字段的對應關系
對應關系
字段參數
null
用于表示某個字段可以為空。
unique
如果設定為unique=True 則該字段在此表中必須是唯一的 。
db_index
如果db_index=True 則代表着為此字段設定資料庫索引。
default
為該字段設定預設值。
時間字段獨有
DatetimeField、DateField、TimeField這個三個時間字段,都可以設定如下屬性。
auto_now_add
配置auto_now_add=True,建立資料記錄的時候會把目前時間添加到資料庫。
auto_now
配置上auto_now=True,每次更新資料記錄的時候會更新該字段。
關系字段
ForeignKey
外鍵類型在ORM中用來表示外鍵關聯關系,一般把ForeignKey字段設定在 '一對多'中'多'的一方。
ForeignKey可以和其他表做關聯關系同時也可以和自身做關聯關系。
字段參數
to
設定要關聯的表
to_field
設定要關聯的表的字段
related_name
反向操作時,使用的字段名,用于代替原反向查詢時的'表名_set'。
例如:
class Classes(models.Model):
name = models.CharField(max_length=32)
class Student(models.Model):
name = models.CharField(max_length=32)
theclass = models.ForeignKey(to="Classes")
當我們要查詢某個班級關聯的所有學生(反向查詢)時,我們會這麼寫:
models.Classes.objects.first().student_set.all()
當我們在ForeignKey字段中添加了參數 related_name 後,
class Student(models.Model):
name = models.CharField(max_length=32)
theclass = models.ForeignKey(to="Classes", related_name="students")
當我們要查詢某個班級關聯的所有學生(反向查詢)時,我們會這麼寫:
models.Classes.objects.first().students.all()
related_query_name
反向查詢操作時,使用的連接配接字首,用于替換表名。
on_delete
當删除關聯表中的資料時,目前表與其關聯的行的行為。
models.CASCADE
删除關聯資料,與之關聯也删除(Django2.0要加上)。
models.DO_NOTHING
删除關聯資料,引發錯誤IntegrityError
models.PROTECT
删除關聯資料,引發錯誤ProtectedError
models.SET_NULL
删除關聯資料,與之關聯的值設定為null(前提FK字段需要設定為可空)
models.SET_DEFAULT
删除關聯資料,與之關聯的值設定為預設值(前提FK字段需要設定預設值)
models.SET
删除關聯資料,
a. 與之關聯的值設定為指定值,設定:models.SET(值)
b. 與之關聯的值設定為可執行對象的傳回值,設定:models.SET(可執行對象)
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User",
to_field="id",
on_delete=models.SET(func)
)
db_constraint
是否在資料庫中建立外鍵限制,預設為True。
OneToOneField
一對一字段。
通常一對一字段用來擴充已有字段。
示例
一對一的關聯關系多用在當一張表的不同字段查詢頻次差距過大的情況下,将本可以存儲在一張表的字段拆開放置在兩張表中,然後将兩張表建立一對一的關聯關系。
class Author(models.Model):
name = models.CharField(max_length=32)
info = models.OneToOneField(to='AuthorInfo')
class AuthorInfo(models.Model):
phone = models.CharField(max_length=11)
email = models.EmailField()
字段參數
to
設定要關聯的表。
to_field
設定要關聯的字段。
on_delete
同ForeignKey字段。
ManyToManyField
用于表示多對多的關聯關系。在資料庫中通過第三張表來建立關聯關系。
字段參數
to
設定要關聯的表
related_name
同ForeignKey字段。
related_query_name
同ForeignKey字段。
symmetrical
僅用于多對多自關聯時,指定内部是否建立反向操作的字段。預設為True。
舉個例子:
class Person(models.Model):
name = models.CharField(max_length=16)
friends = models.ManyToManyField("self")
此時,person對象就沒有person_set屬性。
class Person(models.Model):
name = models.CharField(max_length=16)
friends = models.ManyToManyField("self", symmetrical=False)
此時,person對象現在就可以使用person_set屬性進行反向查詢。
through
在使用ManyToManyField字段時,Django将自動生成一張表來管理多對多的關聯關系。
但我們也可以手動建立第三張表來管理多對多關系,此時就需要通過through來指定第三張表的表名。
through_fields
設定關聯的字段。
db_table
預設建立第三張表時,資料庫中表的名稱。
多對多關聯關系的三種方式
方式一:自行建立第三張表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="書名")
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
# 自己建立第三張表,分别通過外鍵關聯書和作者
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
class Meta:
unique_together = ("author", "book")
方式二:通過ManyToManyField自動建立第三張表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="書名")
# 通過ORM自帶的ManyToManyField自動建立第三張表
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
books = models.ManyToManyField(to="Book", related_name="authors")
方式三:設定ManyTomanyField并指定自行建立的第三張表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="書名")
# 自己建立第三張表,并通過ManyToManyField指定關聯
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
# through_fields接受一個2元組('field1','field2'):
# 其中field1是定義ManyToManyField的模型外鍵的名(author),field2是關聯目标模型(book)的外鍵名。
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
class Meta:
unique_together = ("author", "book")
注意:
當我們需要在第三張關系表中存儲額外的字段時,就要使用第三種方式。
但是當我們使用第三種方式建立多對多關聯關系時,就無法使用set、add、remove、clear方法來管理多對多的關系了,需要通過第三張表的model來管理多對多關系。
元資訊
ORM對應的類裡面包含另一個Meta類,而Meta類封裝了一些資料庫的資訊。主要字段如下:
db_table
ORM在資料庫中的表名預設是 app_類名,可以通過db_table可以重寫表名。
class Meta:
db_table="book"
index_together
聯合索引。
unique_together
聯合唯一索引。
ordering
指定預設按什麼字段排序。
隻有設定了該屬性,我們查詢到的結果才可以被reverse()。
一般操作
官網文檔
必知必會13條
<1> all(): 查詢所有結果
<2> filter(**kwargs): 它包含了與所給篩選條件相比對的對象
<3> get(**kwargs): 傳回與所給篩選條件相比對的對象,傳回結果有且隻有一個,如果符合篩選條件的對象超過一個或者沒有都會抛出錯誤。
<4> exclude(**kwargs): 它包含了與所給篩選條件不比對的對象
<5> values(*field): 傳回一個ValueQuerySet——一個特殊的QuerySet,運作後得到的并不是一系列model的執行個體化對象,而是一個可疊代的字典序列
<6> values_list(*field): 它與values()非常相似,它傳回的是一個元組序列,values傳回的是一個字典序列
<7> order_by(*field): 對查詢結果排序
<8> reverse(): 對查詢結果反向排序,請注意reverse()通常隻能在具有已定義順序的QuerySet上調用(在model類的Meta中指定ordering或調用order_by()方法)。
<9> distinct(): 從傳回結果中剔除重複紀錄(如果你查詢跨越多個表,可能在計算QuerySet時得到重複的結果。此時可以使用distinct(),注意隻有在PostgreSQL中支援按字段去重。)
<10> count(): 傳回資料庫中比對查詢(QuerySet)的對象數量。
<11> first(): 傳回第一條記錄
<12> last(): 傳回最後一條記錄
<13> exists(): 如果QuerySet包含資料,就傳回True,否則傳回False
測試
'''
ORM練習
'''
import os
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mytest.settings')
import django
django.setup()
from app01 import models
# 查詢所有
ret = models.Person.objects.all()
print(ret)
ret = models.Person.objects.get(id=1)
print(ret)
ret = models.Person.objects.get(name="小黑")
print(ret)
# filter
ret = models.Person.objects.filter(name="小黑")
# 用索引方式取出
print(ret[0])
ret = models.Person.objects.filter(id=100)
print(ret)
# exctude
ret = models.Person.objects.exclude(id=1)
print(ret)
# valuse
ret = models.Person.objects.all().values("birthday")
print(ret)
# values_list
ret = models.Person.objects.all().values_list("birthday")
print(ret)
# order by
ret = models.Person.objects.all().order_by("name")
print(ret)
# reverse
ret = ret.reverse()
print(ret)
# count
ret = models.Person.objects.all().count()
print(ret)
# first
ret = models.Person.objects.all().first()
print(ret)
# last
ret = models.Person.objects.all().last()
print(ret)
# exists
ret = models.Person.objects.all().exists()
print(ret)
View Code
傳回QuerySet對象的方法有
all()
filter()
exclude()
order_by()
reverse()
distinct()
特殊的QuerySet
values() 傳回一個可疊代的字典序列
values_list() 傳回一個可疊代的元祖序列
傳回具體對象的
get()
first()
last()
傳回布爾值的方法有:
exists()
傳回數字的方法有
count()
單表查詢之神奇的雙下劃線
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 擷取id大于1 且 小于10的值
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 擷取id等于11、22、33的資料
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
models.Tb1.objects.filter(name__contains="ven") # 擷取name字段包含"ven"的
models.Tb1.objects.filter(name__icontains="ven") # icontains大小寫不敏感
models.Tb1.objects.filter(id__range=[1, 3]) # id範圍是1到3的,等價于SQL的bettwen and
類似的還有:startswith,istartswith, endswith, iendswith
date字段還可以:
models.Class.objects.filter(first_day__year=2017)
ForeignKey操作
正向查找
對象查找(跨表)
文法:
對象.關聯字段.字段
示例:
book_obj = models.Book.objects.first() # 第一本書對象
print(book_obj.publisher) # 得到這本書關聯的出版社對象
print(book_obj.publisher.name) # 得到出版社對象的名稱
字段查找(跨表)
文法:
關聯字段__字段
示例:
print(models.Book.objects.values_list("publisher__name"))
import os
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mytest.settings')
import django
django.setup()
from app01 import models
# 正向查詢
book_obj = models.Book.objects.first()
print(book_obj)
# 和這本書關聯的對象
print(book_obj.publisher)
print(book_obj.publisher.name)
# 查詢id為1的出版社的名稱
# 雙下滑線跨一張表
ret = models.Book.objects.filter(id=1).values("publisher__name")
print(ret)
View Code
反向操作
對象查找
文法:
obj.表名_set
示例:
publisher_obj = models.Publisher.objects.first() # 找到第一個出版社對象
books = publisher_obj.book_set.all() # 找到第一個出版社出版的所有書
titles = books.values_list("title") # 找到第一個出版社出版的所有書的書名
可以在建表時進行設定就不用表名_set,設定related_name="books"
class Book(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=30)
publisher = models.ForeignKey(
to="Publisher",
on_delete=models.CASCADE,
db_constraint=False,
related_name="books")
View Code
可以使用 author_obj.books
import os
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mytest.settings')
import django
django.setup()
from app01 import models
# 多對多
# create
author_obj = models.Author.objects.first()
author_obj.books.create(title="活着",publisher_id=2)
View Code
字段查找
文法:
表名__字段
示例:
titles = models.Publisher.objects.values_list("book__title")
ManyToManyField
class RelatedManager
"關聯管理器"是在一對多或者多對多的關聯上下文中使用的管理器。
它存在于下面兩種情況:
- 外鍵關系的反向查詢
- 多對多關聯關系
簡單來說就是當 點後面的對象 可能存在多個的時候就可以使用以下的方法。
方法
create()
建立一個新的對象,儲存對象,并将它添加到關聯對象集之中,傳回新建立的對象。
>>> import datetime
>>> models.Author.objects.first().book_set.create(title="番茄物語", publish_date=datetime.date.today())
add()
把指定的model對象添加到關聯對象集中。
添加對象 (清單打散傳入)
>>> author_objs = models.Author.objects.filter(id__lt=3)
>>> models.Book.objects.first().authors.add(*author_objs)
添加id
>>> models.Book.objects.first().authors.add(*[1, 2])
set()
更新model對象的關聯對象。
>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.set([2, 3])
remove()
從關聯對象集中移除執行的model對象
>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.remove(3)
clear()
從關聯對象集中移除一切對象。
>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.clear()
注意:
對于ForeignKey對象,clear()和remove()方法僅在null=True時存在。
舉個例子:
ForeignKey字段沒設定null=True時,
class Book(models.Model):
title = models.CharField(max_length=32)
publisher = models.ForeignKey(to=Publisher)
沒有clear()和remove()方法:
>>> models.Publisher.objects.first().book_set.clear()
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'clear'
當ForeignKey字段設定null=True時,
class Book(models.Model):
name = models.CharField(max_length=32)
publisher = models.ForeignKey(to=Class, null=True)
此時就有clear()和remove()方法:
>>> models.Publisher.objects.first().book_set.clear()
注意:
- 對于所有類型的關聯字段,add()、create()、remove()和clear(),set()都會馬上更新資料庫。換句話說,在關聯的任何一端,都不需要再調用save()方法。
聚合查詢和分組查詢
聚合
aggregate()是QuerySet 的一個終止子句,意思是說,它傳回一個包含一些鍵值對的字典。
鍵的名稱是聚合值的辨別符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。
用到的内置函數:
from django.db.models import Avg, Sum, Max, Min, Count
示例:
>>> from django.db.models import Avg, Sum, Max, Min, Count
>>> models.Book.objects.all().aggregate(Avg("price"))
{'price__avg': 13.233333}
如果你想要為聚合值指定一個名稱,可以向聚合子句提供它。
>>> models.Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 13.233333}
如果你希望生成不止一個聚合,你可以向aggregate()子句中添加另一個參數。是以,如果你也想知道所有圖書價格的最大值和最小值,可以這樣查詢:
>>> models.Book.objects.all().aggregate(Avg("price"), Max("price"), Min("price"))
{'price__avg': 13.233333, 'price__max': Decimal('19.90'), 'price__min': Decimal('9.90')}
分組
我們在這裡先複習一下SQL語句的分組。
假設現在有一張公司職員表:
我們使用原生SQL語句,按照部分分組求平均工資:
select dept,AVG(salary) from employee group by dept;
ORM查詢:
from django.db.models import Avg
Employee.objects.values("dept").annotate(avg=Avg("salary").values(dept, "avg")
連表查詢的分組:
SQL查詢:
select dept.name,AVG(salary) from employee inner join dept on (employee.dept_id=dept.id) group by dept_id;
ORM查詢:
from django.db.models import Avg
models.Dept.objects.annotate(avg=Avg("employee__salary")).values("name", "avg")
更多示例:
示例1:統計每一本書的作者個數
>>> book_list = models.Book.objects.all().annotate(author_num=Count("author"))
>>> for obj in book_list:
... print(obj.author_num)
...
2
1
1
示例2:統計出每個出版社買的最便宜的書的價格
>>> publisher_list = models.Publisher.objects.annotate(min_price=Min("book__price"))
>>> for obj in publisher_list:
... print(obj.min_price)
...
9.90
19.90
方法二:
>>> models.Book.objects.values("publisher__name").annotate(min_price=Min("price"))
示例3:統計不止一個作者的圖書
>>> models.Book.objects.annotate(author_num=Count("author")).filter(author_num__gt=1)
<QuerySet [<Book: 番茄物語>]>
示例4:根據一本圖書作者數量的多少對查詢集 QuerySet進行排序
>>> models.Book.objects.annotate(author_num=Count("author")).order_by("author_num")
<QuerySet [<Book: 香蕉物語>, <Book: 橘子物語>, <Book: 番茄物語>]>
示例5:查詢各個作者出的書的總價格
>>> models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price")
<QuerySet [{'name': '小精靈', 'sum_price': Decimal('9.90')}, {'name': '小仙女', 'sum_price': Decimal('29.80')}, {'name': '小魔女', 'sum_price': Decimal('9.90')}]>
F查詢和Q查詢
F查詢
在上面所有的例子中,我們構造的過濾器都隻是将字段值與某個常量做比較。如果我們要對兩個字段的值做比較,那該怎麼做呢?
Django 提供 F() 來做這樣的比較。F() 的執行個體可以在查詢中引用字段,來比較同一個 model 執行個體中兩個不同字段的值。
示例1:
查詢評論數大于收藏數的書籍
from django.db.models import F
models.Book.objects.filter(commnet_num__gt=F('keep_num'))
Django 支援 F() 對象之間以及 F() 對象和常數之間的加減乘除和取模的操作。
models.Book.objects.filter(commnet_num__lt=F('keep_num')*2)
修改操作也可以使用F函數,比如将每一本書的價格提高30元
models.Book.objects.all().update(price=F("price")+30)
引申:
如果要修改char字段咋辦?
如:把所有書名後面加上(第一版)
>>> from django.db.models.functions import Concat
>>> from django.db.models import Value
>>> models.Book.objects.all().update(title=Concat(F("title"), Value("("), Value("第一版"), Value(")")))
Q查詢
filter() 等方法中的關鍵字參數查詢都是一起進行“AND” 的。 如果你需要執行更複雜的查詢(例如OR語句),你可以使用Q對象。
示例1:
查詢作者名是小仙女或小魔女的
models.Book.objects.filter(Q(authors__name="小仙女")|Q(authors__name="小魔女"))
你可以組合& 和| 操作符以及使用括号進行分組來編寫任意複雜的Q 對象。同時,Q 對象可以使用~ 操作符取反,這允許組合正常的查詢和取反(NOT) 查詢。
示例:查詢作者名字是小仙女并且不是2018年出版的書的書名。
>>> models.Book.objects.filter(Q(author__name="小仙女") & ~Q(publish_date__year=2018)).values_list("title")
<QuerySet [('番茄物語',)]>
查詢函數可以混合使用Q 對象和關鍵字參數。所有提供給查詢函數的參數(關鍵字參數或Q 對象)都将"AND”在一起。但是,如果出現Q 對象,它必須位于所有關鍵字參數的前面。
例如:查詢出版年份是2017或2018,書名中帶物語的所有書。
>>> models.Book.objects.filter(Q(publish_date__year=2018) | Q(publish_date__year=2017), title__icontains="物語")
<QuerySet [<Book: 番茄物語>, <Book: 香蕉物語>, <Book: 橘子物語>]>
鎖和事務
鎖
select_for_update(nowait=False, skip_locked=False)
傳回一個鎖住行直到事務結束的查詢集,如果資料庫支援,它将生成一個 SELECT ... FOR UPDATE 語句。
舉個例子:
entries = Entry.objects.select_for_update().filter(author=request.user)
所有比對的行将被鎖定,直到事務結束。這意味着可以通過鎖防止資料被其它事務修改。
一般情況下如果其他事務鎖定了相關行,那麼本查詢将被阻塞,直到鎖被釋放。 如果這不想要使查詢阻塞的話,使用select_for_update(nowait=True)。 如果其它事務持有沖突的鎖, 那麼查詢将引發 DatabaseError 異常。你也可以使用select_for_update(skip_locked=True)忽略鎖定的行。 nowait和skip_locked是互斥的,同時設定會導緻ValueError。
目前,postgresql,oracle和mysql資料庫後端支援select_for_update()。 但是,MySQL不支援nowait和skip_locked參數。
使用不支援這些選項的資料庫後端(如MySQL)将nowait=True或skip_locked=True轉換為select_for_update()将導緻抛出DatabaseError異常,這可以防止代碼意外終止。
事務
import os
if __name__ == '__main__':
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
import django
django.setup()
import datetime
from app01 import models
try:
from django.db import transaction
with transaction.atomic():
new_publisher = models.Publisher.objects.create(name="火星出版社")
models.Book.objects.create(title="橘子物語", publish_date=datetime.date.today(), publisher_id=10) # 指定一個不存在的出版社id
except Exception as e:
print(str(e))
其他操作
Django ORM執行原生SQL
在模型查詢API不夠用的情況下,我們還可以使用原始的SQL語句進行查詢。
Django 提供兩種方法使用原始SQL進行查詢:一種是使用raw()方法,進行原始SQL查詢并傳回模型執行個體;另一種是完全避開模型層,直接執行自定義的SQL語句。
執行原生查詢
raw()管理器方法用于原始的SQL查詢,并傳回模型的執行個體:
注意:raw()文法查詢必須包含主鍵。
這個方法執行原始的SQL查詢,并傳回一個django.db.models.query.RawQuerySet 執行個體。 這個RawQuerySet 執行個體可以像一般的QuerySet那樣,通過疊代來提供對象執行個體。
舉個例子:
class Person(models.Model):
first_name = models.CharField(...)
last_name = models.CharField(...)
birth_date = models.DateField(...)
可以像下面這樣執行原生SQL語句
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
... print(p)
raw()查詢可以查詢其他表的資料。
舉個例子:
ret = models.Student.objects.raw('select id, tname as hehe from app02_teacher')
for i in ret:
print(i.id, i.hehe)
raw()方法自動将查詢字段映射到模型字段。還可以通過translations參數指定一個把查詢的字段名和ORM對象執行個體的字段名互相對應的字典
d = {'tname': 'haha'}
ret = models.Student.objects.raw('select * from app02_teacher', translations=d)
for i in ret:
print(i.id, i.sname, i.haha)
原生SQL還可以使用參數,注意不要自己使用字元串格式化拼接SQL語句,防止SQL注入!
d = {'tname': 'haha'}
ret = models.Student.objects.raw('select * from app02_teacher where id > %s', translations=d, params=[1,])
for i in ret:
print(i.id, i.sname, i.haha)
直接執行自定義SQL
有時候raw()方法并不十分好用,很多情況下我們不需要将查詢結果映射成模型,或者我們需要執行DELETE、 INSERT以及UPDATE操作。在這些情況下,我們可以直接通路資料庫,完全避開模型層。
我們可以直接從django提供的接口中擷取資料庫連接配接,然後像使用pymysql子產品一樣操作資料庫。
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
ret = cursor.fetchone()
QuerySet方法大全
##################################################################
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
##################################################################
def all(self)
# 擷取所有的資料對象
def filter(self, *args, **kwargs)
# 條件查詢
# 條件可以是:參數,字典,Q
def exclude(self, *args, **kwargs)
# 條件查詢
# 條件可以是:參數,字典,Q
def select_related(self, *fields)
性能相關:表之間進行join連表操作,一次性擷取關聯的資料。
總結:
1. select_related主要針一對一和多對一關系進行優化。
2. select_related使用SQL的JOIN語句進行優化,通過減少SQL查詢的次數來進行優化、提高性能。
def prefetch_related(self, *lookups)
性能相關:多表連表操作時速度會慢,使用其執行多次SQL查詢在Python代碼中實作連表操作。
總結:
1. 對于多對多字段(ManyToManyField)和一對多字段,可以使用prefetch_related()來進行優化。
2. prefetch_related()的優化方式是分别查詢每個表,然後用Python處理他們之間的關系。
def annotate(self, *args, **kwargs)
# 用于實作聚合group by查詢
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
def distinct(self, *field_names)
# 用于distinct去重
models.UserInfo.objects.values('nid').distinct()
# select distinct nid from userinfo
注:隻有在PostgreSQL中才能使用distinct進行去重
def order_by(self, *field_names)
# 用于排序
models.UserInfo.objects.all().order_by('-id','age')
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# 構造額外的查詢條件或者映射,如:子查詢
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
def reverse(self):
# 倒序
models.UserInfo.objects.all().order_by('-nid').reverse()
# 注:如果存在order_by,reverse則是倒序,如果多個排序則一一倒序
def defer(self, *fields):
models.UserInfo.objects.defer('username','id')
或
models.UserInfo.objects.filter(...).defer('username','id')
#映射中排除某列資料
def only(self, *fields):
#僅取某個表中的資料
models.UserInfo.objects.only('username','id')
或
models.UserInfo.objects.filter(...).only('username','id')
def using(self, alias):
指定使用的資料庫,參數為别名(setting中的設定)
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################
def raw(self, raw_query, params=None, translations=None, using=None):
# 執行原生SQL
models.UserInfo.objects.raw('select * from userinfo')
# 如果SQL是其他表時,必須将名字設定為目前UserInfo對象的主鍵列名
models.UserInfo.objects.raw('select id as nid from 其他表')
# 為原生SQL設定參數
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
# 将擷取的到列名轉換為指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
# 指定資料庫
models.UserInfo.objects.raw('select * from userinfo', using="default")
################### 原生SQL ###################
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone() # fetchall()/fetchmany(..)
def values(self, *fields):
# 擷取每行資料為字典格式
def values_list(self, *fields, **kwargs):
# 擷取每行資料為元祖
def dates(self, field_name, kind, order='ASC'):
# 根據時間進行某一部分進行去重查找并截取指定内容
# kind隻能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order隻能是:"ASC" "DESC"
# 并擷取轉換後的時間
- year : 年-01-01
- month: 年-月-01
- day : 年-月-日
models.DatePlus.objects.dates('ctime','day','DESC')
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根據時間進行某一部分進行去重查找并截取指定内容,将時間轉換為指定時區時間
# kind隻能是 "year", "month", "day", "hour", "minute", "second"
# order隻能是:"ASC" "DESC"
# tzinfo時區對象
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
def none(self):
# 空QuerySet對象
####################################
# METHODS THAT DO DATABASE QUERIES #
####################################
def aggregate(self, *args, **kwargs):
# 聚合函數,擷取字典類型聚合結果
from django.db.models import Count, Avg, Max, Min, Sum
result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}
def count(self):
# 擷取個數
def get(self, *args, **kwargs):
# 擷取單個對象
def create(self, **kwargs):
# 建立對象
def bulk_create(self, objs, batch_size=None):
# 批量插入
# batch_size表示一次插入的個數
objs = [
models.DDD(name='r11'),
models.DDD(name='r22')
]
models.DDD.objects.bulk_create(objs, 10)
def get_or_create(self, defaults=None, **kwargs):
# 如果存在,則擷取,否則,建立
# defaults 指定建立時,其他字段的值
obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
def update_or_create(self, defaults=None, **kwargs):
# 如果存在,則更新,否則,建立
# defaults 指定建立時或更新時的其他字段
obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
def first(self):
# 擷取第一個
def last(self):
# 擷取最後一個
def in_bulk(self, id_list=None):
# 根據主鍵ID進行查找
id_list = [11,21,31]
models.DDD.objects.in_bulk(id_list)
def delete(self):
# 删除
def update(self, **kwargs):
# 更新
def exists(self):
# 是否有結果
QuerySet
Django終端列印SQL語句
在Django項目的settings.py檔案中,在最後複制粘貼如下代碼:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
即為你的Django項目配置上一個名為django.db.backends的logger執行個體即可檢視翻譯後的SQL語句。
在Python腳本中調用Django環境
import os
if __name__ == '__main__':
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
import django
django.setup()
from app01 import models
books = models.Book.objects.all()
print(books)
轉載于:https://www.cnblogs.com/wangzihong/p/10178676.html