1. 程式人生 > 其它 >Pycharm開發Django專案查詢操作

Pycharm開發Django專案查詢操作

查詢操作

查詢是資料庫操作中一個非常重要的技術。查詢一般就是使用filterexclude以及get三個方法來實現。我們可以在呼叫這些方法的時候傳遞不同的引數來實現查詢需求。在ORM層面,這些查詢條件都是使用field+__+condition的方式來使用的。以下將那些常用的查詢條件來一一解釋。

查詢條件

exact:

使用精確的=進行查詢。如果提供的是一個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進行查詢。示例程式碼如下:

article = Article.objects.filter(title__iexact='hello world')

那麼以上的查詢就等價於以下的SQL語句:

select ... from article where title like 'hello world';

注意上面這個sql語句,因為在MySQL中,沒有一個叫做ilike

的。所以exactiexact的區別實際上就是LIKE=的區別,在大部分collation=utf8_general_ci情況下都是一樣的(collation是用來對字串比較的)。

contains:

大小寫敏感,判斷某個欄位是否包含了某個資料。示例程式碼如下:

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的值是否在給定的容器中。容器可以為listtuple或者任何一個可以迭代的物件,包括QuerySet物件。示例程式碼如下:

articles = Article.objects.filter(id__in=[1,2,3])

以上程式碼在翻譯成SQL語句為如下:

select ... where id in (1,3,4)

當然也可以傳遞一個QuerySet物件進去。示例程式碼如下:

inner_qs = Article.objects.filter(title__contains='hello')
categories = Category.objects.filter(article__in=inner_qs)

以上程式碼的意思是獲取那些文章標題包含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')

以上程式碼的意思是提取所有標題以hello字串開頭的文章。
將翻譯成以下SQL語句:

select ... where title like 'hello%'

istartswith:

類似於startswith,但是大小寫是不敏感的。

endswith:

判斷某個欄位的值是否以某個值結束。大小寫敏感。示例程式碼如下:

articles = Article.objects.filter(title__endswith='world')

以上程式碼的意思是提取所有標題以world結尾的文章。
將翻譯成以下SQL語句:

select ... where title like '%world';

iendswith:

類似於endswith,只不過大小寫不敏感。

range:

判斷某個field的值是否在給定的區間中。示例程式碼如下:

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))

以上程式碼的意思是提取所有釋出時間在2018/1/12018/12/12之間的文章。
將翻譯成以下的SQL語句:

select ... from article where pub_time between '2018-01-01' and '2018-12-12'。

需要注意的是,以上提取資料,不會包含最後一個值。也就是不會包含2018/12/12的文章。
而且另外一個重點,因為我們在settings.py中指定了USE_TZ=True,並且設定了TIME_ZONE='Asia/Shanghai',因此我們在提取資料的時候要使用django.utils.timezone.make_aware先將datetime.datetimenavie時間轉換為aware時間。make_aware會將指定的時間轉換為TIME_ZONE中指定的時區的時間。

date:

針對某些date或者datetime型別的欄位。可以指定date的範圍。並且這個時間過濾,還可以使用鏈式呼叫。示例程式碼如下:

articles = Article.objects.filter(pub_date__date=date(2018,3,29))

以上程式碼的意思是查詢時間為2018/3/29這一天發表的所有文章。
將翻譯成以下的sql語句:

select ... WHERE DATE(CONVERT_TZ(`front_article`.`pub_date`, 'UTC', 'Asia/Shanghai')) = 2018-03-29

注意,因為預設情況下MySQL的表中是沒有儲存時區相關的資訊的。因此我們需要下載一些時區表的檔案,然後新增到Mysql的配置路徑中。如果你用的是windows作業系統。那麼在http://dev.mysql.com/downloads/timezones.html下載timezone_2018d_posix.zip - POSIX standard。然後將下載下來的所有檔案拷貝到C:\ProgramData\MySQL\MySQL Server 5.7\Data\mysql中,如果提示檔名重複,那麼選擇覆蓋即可。
如果用的是linux或者mac系統,那麼在命令列中執行以下命令:mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p,然後輸入密碼,從系統中載入時區檔案更新到mysql中。

year:

根據年份進行查詢。示例程式碼如下:

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,根據月份進行查詢。

day:

year,根據日期進行查詢。

week_day:

Django 1.11新增的查詢方式。同year,根據星期幾進行查詢。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。

time:

根據時間進行查詢。示例程式碼如下:

articles = Article.objects.filter(pub_date__time=datetime.time(12,12,12));

以上的程式碼是獲取每一天中12點12分12秒發表的所有文章。
更多的關於時間的過濾,請參考Django官方文件:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#range

isnull:

根據值是否為空進行查詢。示例程式碼如下:

articles = Article.objects.filter(pub_date__isnull=False)

以上的程式碼的意思是獲取所有釋出日期不為空的文章。
將來翻譯成SQL語句如下:

select ... where pub_date is not null;

regex和iregex:

大小寫敏感和大小寫不敏感的正則表示式。示例程式碼如下:

articles = Article.objects.filter(title__regex=r'^hello')

以上程式碼的意思是提取所有標題以hello字串開頭的文章。
將翻譯成以下的SQL語句:

select ... where title regexp binary '^hello';

iregex是大小寫不敏感的。

根據關聯的表進行查詢:

假如現在有兩個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)

比如想要獲取文章標題中包含"hello"的所有的分類。那麼可以通過以下程式碼來實現:

categories = Category.object.filter(article__title__contains("hello"))

聚合函式:

如果你用原生SQL,則可以使用聚合函式來提取資料。比如提取某個商品銷售的數量,那麼可以使用Count,如果想要知道商品銷售的平均價格,那麼可以使用Avg
聚合函式是通過aggregate方法來實現的。在講解這些聚合函式的用法的時候,都是基於以下的模型物件來實現的。

  from django.db import models

 class 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'
  1. Avg:求平均值。比如想要獲取所有圖書的價格平均值。那麼可以使用以下程式碼實現。

     from django.db.models import Avg
     result = Book.objects.aggregate(Avg('price'))
     print(result)
    

    以上的列印結果是:

     {"price__avg":23.0}
    

    其中price__avg的結構是根據field__avg規則構成的。如果想要修改預設的名字,那麼可以將Avg賦值給一個關鍵字引數。示例程式碼如下:

     from django.db.models import Avg
     result = Book.objects.aggregate(my_avg=Avg('price'))
     print(result)
    

    那麼以上的結果列印為:

     {"my_avg":23}
    
  2. Count:獲取指定的物件的個數。示例程式碼如下:

     from django.db.models import Count
     result = Book.objects.aggregate(book_num=Count('id'))
    

    以上的result將返回Book表中總共有多少本圖書。
    Count類中,還有另外一個引數叫做distinct,預設是等於False,如果是等於True,那麼將去掉那些重複的值。比如要獲取作者表中所有的不重複的郵箱總共有多少個,那麼可以通過以下程式碼來實現:

         from djang.db.models import Count
         result = Author.objects.aggregate(count=Count('email',distinct=True))
    
  3. MaxMin:獲取指定物件的最大值和最小值。比如想要獲取Author表中,最大的年齡和最小的年齡分別是多少。那麼可以通過以下程式碼來實現:

     from django.db.models import Max,Min
     result = Author.objects.aggregate(Max('age'),Min('age'))
    

    如果最大的年齡是88,最小的年齡是18。那麼以上的result將為:

     {"age__max":88,"age__min":18}
    
  4. Sum:求指定物件的總和。比如要求圖書的銷售總額。那麼可以使用以下程式碼實現:

     from djang.db.models import Sum
     result = Book.objects.annotate(total=Sum("bookstore__price")).values("name","total")
    

    以上的程式碼annotate的意思是給Book表在查詢的時候新增一個欄位叫做total,這個欄位的資料來源是從BookStore模型的price的總和而來。values方法是隻提取nametotal兩個欄位的值。

更多的聚合函式請參考官方文件:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions

aggregate和annotate的區別:

  1. aggregate:返回使用聚合函式後的欄位和值。

  2. annotate:在原來模型欄位的基礎之上新增一個使用了聚合函式的欄位,並且在使用聚合函式的時候,會使用當前這個模型的主鍵進行分組(group by)。
    比如以上Sum的例子,如果使用的是annotate,那麼將在每條圖書的資料上都新增一個欄位叫做total,計算這本書的銷售總額。
    而如果使用的是aggregate,那麼將求所有圖書的銷售總額。

F表示式和Q表示式:

F表示式:

F表示式是用來優化ORM操作資料庫的。比如我們要將公司所有員工的薪水都增加1000元,如果按照正常的流程,應該是先從資料庫中提取所有的員工工資到Python記憶體中,然後使用Python程式碼在員工工資的基礎之上增加1000元,最後再儲存到資料庫中。這裡面涉及的流程就是,首先從資料庫中提取資料到Python記憶體中,然後在Python記憶體中做完運算,之後再儲存到資料庫中。示例程式碼如下:

employees = Employee.objects.all()
for employee in employees:
    employee.salary += 1000
    employee.save()

而我們的F表示式就可以優化這個流程,他可以不需要先把資料從資料庫中提取出來,計算完成後再儲存回去,他可以直接執行SQL語句,就將員工的工資增加1000元。示例程式碼如下:

from djang.db.models import F
Employee.object.update(salary=F("salary")+1000)

F表示式並不會馬上從資料庫中獲取資料,而是在生成SQL語句的時候,動態的獲取傳給F表示式的值。

比如如果想要獲取作者中,nameemail相同的作者資料。如果不使用F表示式,那麼需要使用以下程式碼來完成:

    authors = Author.objects.all()
    for author in authors:
        if author.name == author.email:
            print(author)

如果使用F表示式,那麼一行程式碼就可以搞定。示例程式碼如下:

    from django.db.models import F
    authors = Author.objects.filter(name=F("email"))

Q表示式:

如果想要實現所有價格高於100元,並且評分達到9.0以上評分的圖書。那麼可以通過以下程式碼來實現:

books = Book.objects.filter(price__gte=100,rating__gte=9)

以上這個案例是一個並集查詢,可以簡單的通過傳遞多個條件進去來實現。
但是如果想要實現一些複雜的查詢語句,比如要查詢所有價格低於10元,或者是評分低於9分的圖書。那就沒有辦法通過傳遞多個條件進去實現了。這時候就需要使用Q表示式來實現了。示例程式碼如下:

from django.db.models import Q
books = Book.objects.filter(Q(price__lte=10) | Q(rating__lte=9))

以上是進行或運算,當然還可以進行其他的運算,比如有&~(非)等。一些用Q表示式的例子如下:

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))