1. 程式人生 > 其它 >2022.5.18 ORM查詢及操作MySQL

2022.5.18 ORM查詢及操作MySQL

2022.5.18 ORM查詢及操作MySQL

  • 聚合查詢
  • 分組查詢
  • F與Q查詢
  • ORM查詢優化
  • ORM常見欄位及引數
  • ORM事務操作
  • ORM執行原生SQL語句
  • 多對多三種建立方式

一、聚合查詢

MySQL聚合函式:max\min\sum\count\avg
    
# 匯入模組
from django.db.models import Max, Min, Sum, Avg, Count

# ORM操作
1.
res = models.Book.objects.aggregate(Max('price'))
print(res)  # {'price__max': Decimal('56777.98')}
ps:沒有分組也可以使用聚合函式,預設整體就是一組;

2.aggregate括號內也可使用多個聚合函式:
res = models.Book.objects.aggregate(Max('price'),
                                    Min('price'),
                                    Sum('price'),
                                    Avg('price'),
                                    Count('pk'))
print(res)
# {'price__max': Decimal('56777.98'), 'price__min': Decimal('16987.22'), 'price__sum': Decimal('179408.51'), 'price__avg': 29901.418333, 'pk__count': 6}

二、分組查詢

主要用於有外來鍵欄位的多張表的資料查詢,可以通過按照一張表的分組獲取另一張表的資料

MySQL分組操作:group by
django ORM操作:annotate()
ps:ORM執行分組操作時如果報錯,可能需要修改sql_mode,然後移除only_full_group_by;
    

案例展示:
# 1.統計每本書的作者個數
from django.db.models import Count
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')  # 使用表物件點annotate,就是按照表的資料分組,括號裡面是分組後的操作或者獲取資料

# 2.統計每個出版社賣的最便宜的書的價格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')  # 按照出版社每個物件分組,並獲取最便宜書籍的最小值,使用values獲取值

# 3.統計不止一個作者的圖書
from django.db.models import Count
res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title','author_num')

# 4.統計每個作者出的書的總價格
from django.db.models import Sum
res = models.Author.objects.annotate(book_sum_price=Sum('book__price')).values('name','book_sum_price')

"""上述操作都是以表為單位做分組 如果想要以表中的某個欄位分組如何操作"""
models.Author.objects.values('age').annotate()

案例:
# 統計每個出版社主鍵值對應的書籍個數
from django.db.models import Count
res = models.Book.objects.values('publish_id').annotate(book_num=Count('pk')).values('publish_id','book_num')
print(res)

三、F與Q查詢

# 當表中已經有資料的情況下,新增額外的欄位,需要指定預設值或者可以為null:
	方式1
		IntegerField(verbose_name='銷量',default=1000)
	方式2
		IntegerField(verbose_name='銷量',null=True)
	方式3
		在遷移命令提示中直接給預設值

1、F查詢

案例分析:
# 1.查詢庫存大於銷量的書籍
res = models.Book.objects.filter(kucun > maichu)    不行
res = models.Book.objects.filter(kucun__gt=maichu)  不行
    # 當查詢條件的左右兩表的資料都需要表中的資料,可以使用F查詢
    from django.db.models import F
    res = models.Book.objects.filter(kucun__gt=F('maichu'))

# 2.將所有書的價格提升1000塊
res = models.Book.objects.update(price=F('price') + 1000)

# 3.將所有書的名稱後面加上_爆款字尾
res = models.Book.objects.update(title=F('title') + '_爆款')  # 不可以
    # 如果要修改char欄位咋辦(千萬不能用上面對數值型別的操作!!!),需要使用下列兩個方法:
    from django.db.models.functions import Concat
    from django.db.models import Value
    res = models.Book.objects.update(name=Concat(F('title'), Value('爆款')))

2、Q查詢

案例分析:
# 查詢價格大於20000或者賣出大於1000的書籍
    res = models.Book.objects.filter(price__gt=20000,maichu__gt=1000)
    print(res)
    print(res.query)
    '''filter括號內多個條件預設是and關係,無法直接修改'''
    '''使用Q物件,就可以支援邏輯運算子'''
    from django.db.models import Q
    res = models.Book.objects.filter(Q(price__gt=20000), Q(maichu__gt=1000))  # 逗號是and關係
    res = models.Book.objects.filter(Q(price__gt=20000) | Q(maichu__gt=1000))  # 管道符是or關係
    res = models.Book.objects.filter(~Q(price__gt=20000))  # ~是not操作
    print(res.query)
    
# Q物件進階用法
    filter(price=100)
    filter('price'=100)
    當我們需要編寫一個搜尋功能,並且條件是由使用者指定,這個時候左邊的資料就是一個字串,無法由使用者指定,若想實現可以使用Q物件:
    q_obj = Q()  # 獲取一個Q物件
    q_obj.connector = 'or'  # 預設是and,可以改為or
    q_obj.children.append(('price__gt',20000))  # 注意括號內是元組形式;
    q_obj.children.append(('maichu__gt',1000))
    res = models.Book.objects.filter(q_obj)  # q_obj可以承載多個條件
    print(res.query)

四、ORM查詢優化

1、ORM瞭解

"""
在IT行業 針對資料庫 需要儘量做 能不'麻煩'它就不'麻煩'它
"""
# 1.orm查詢預設都是惰性查詢(能不消耗資料庫資源就不消耗)
	光編寫orm語句並不會直接指向SQL語句,只有後續的程式碼用到了才會執行;
# 2.orm查詢預設自帶分頁功能(儘量減輕單次查詢資料的壓力)

2、only與defer

# only
    案例分析:
    res = models.Book.objects.values('title','price')
    # 需求:讓上述單個結果還是以物件的形式展示,可以直接通過句點符操作
    for i in res:
        print(i.get('title'))  # 結果不是一個物件

    res = models.Book.objects.only('title', 'price')  # 結果是物件
    for obj in res:
       print(obj.title)
       print(obj.price)
       print(obj.publish_time)
    """
        only會產生物件結果集,物件點括號內出現的欄位不會再走資料庫查詢;
        但是如果點選了括號內沒有的欄位也可以獲取到資料,但是每次都會走資料庫查詢;
        """
    
# defer
    案例分析
    res = models.Book.objects.defer('title','price')
    for obj in res:
        print(obj.title)
        print(obj.price)
        print(obj.publish_time)
    """
    defer與only剛好相反,物件點括號內出現的欄位會走資料庫查詢;
    如果點選了括號內沒有的欄位也可以獲取到資料,每次都不會走資料庫查詢;
    """

總結:

(1)only和defer都可以通過欄位名產生物件,而不是對應資料;

(2)only和defer產生的物件在和資料庫互動走資料庫查詢是完全相反的,only產生的物件後臺可以不通過資料庫查詢獲取only指定的資料,而defer產生的物件後臺必須通過資料庫查詢獲取defer指定的資料;

# select_related
        res = models.Book.objects.select_related('publish')
        for obj in res:
            print(obj.title)  # 獲取book表內容
            print(obj.publish.name)  # 獲取publish表內容
            print(obj.publish.addr)
    """
    (1)select_related括號內只能傳一對一和一對多欄位,不能傳多對多欄位;
    (2)效果是內部直接連線表(inner join),然後將連線之後的大表中所有的資料全部封裝到資料物件中;
    (3)後續物件通過正反向查詢跨表,內部不會再走資料庫查詢;
    """
    
# prefetch_related
    res = models.Book.objects.prefetch_related('publish')
    for obj in res:
        print(obj.title)
        print(obj.publish.name)
        print(obj.publish.addr)
    """
    (1)prefetch_related主要針一對多和多對多關係進行優化;
    (2)prefetch_related通過分別獲取各個表的內容,然後用Python處理他們之間的關係來進行優化;
    (3)效果類似於多次子查詢,將多次查詢之後的結果封裝到資料物件中,後續物件通過正反向查詢跨表,內部不會再走資料庫查詢;
    """

總結:

(1)select_related是通過join來關聯多表,一次獲取資料,存放在記憶體中,但如果關聯的表太多,會嚴重影響資料庫效能。

(2)prefetch_related是通過分表,先獲取各個表的資料,存放在記憶體中,然後通過Python處理他們之間的關聯。

五、ORM常見欄位及引數

1、常見欄位

AutoField()
	int auto_increment
CharField()
	必須提供max_length引數	對應的資料庫中是varchar型別
IntergerField()
	int
DecimalField()
	decimal
DateField()
	date				auto_now   auto_now_add
DateTimeField()
	datetime			auto_now   auto_now_add
BigIntergerField()
	bigint
BooleanField()
	傳佈爾值 存0和1
TextField()
	儲存大段文字
FileField()
	傳檔案自動儲存到指定位置並存檔案路徑
EmailField()
	本質還是varchar型別 
 
# 自定義欄位型別
class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):
        return 'char(%s)' % self.max_length

2、重要引數

primary_key  # 主鍵 primary_key=True

"字元型欄位燦數:"
max_length  # 字元長度 max_length=32
verbose_name  # 欄位別名 verbose_name='xxx'
null  # 預設為空 null=True
default  # 設定預設值 default='xxx'

"int型欄位引數:"
max_digits
decimal_places

"DateField等時間欄位型別引數"
unique  # 設定唯一欄位 unique=True
db_index
auto_now  # 自動獲取當前時間,並在修改時更新
auto_now_add  # 自動獲取當前時間,後續不自動改變auto_now_add=True

choices
	用於可以被列舉完全的資料,類似於列舉;
  	eg:性別 學歷 工作經驗 工作狀態
      	class User(models.Model):
            username = models.CharField(max_length=32)
            password = models.IntegerField()
            gender_choice = (
                (1,'男性'),
                (2,'女性'),
                (3,'變性')
            )
            gender = models.IntegerField(choices=gender_choice)
            
		# user_obj.get_gender_display()  
		# 欄位預設設定為數字,通過get_gender_display()方法獲取元組內指定資料,元組內有對應關係就拿,沒有就還是本身的數字;
        
"外來鍵欄位引數:"
to  # to=外來鍵表名
to_field  # to_field=外來鍵表的欄位名
db_constraint
ps:外來鍵欄位中可能還會遇到related_name引數
"""
外來鍵欄位中使用related_name引數可以修改正向查詢的欄位名,相當於給外來鍵起別名;
"""

六、ORM事務操作

# 回顧MySQL
    MySQL事務:四大特性(ACID)
    原子性
    一致性
    獨立性
    永續性
    
    start transcation;
    rollback;
    commit;
    
# ORM事務操作
    from django.db import transaction
    try:
        with transaction.atomic():
            pass  # 事務執行的程式碼一旦出現異常整個with內部的資料庫操作都會自動回滾(rollback)
    except Exception:
            pass

七、Django執行原生SQL語句

# 方式1:獲取cursor執行
from django.db import connection, connections
cursor = connection.cursor()  
cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])  # 執行sql語句
cursor.fetchone()  # 獲取資料

# 方式2:ORM操作執行--extra()
models.UserInfo.objects.extra(
                    select={'newid':'select count(1) from app01_usertype where id>%s'},
                    select_params=[1,],
                    where = ['age>%s'],
                    params=[18,],
                    order_by=['-age'],
                    tables=['app01_usertype']
                )  # 直接在ORM操作中利用extra操作sql語句

八、多對多三種建立方式

# 全自動(常見)
	orm自動建立第三張表,但是無法擴充套件第三張表的欄位;
	authors = models.ManyToManyField(to='Author')
# 全手動(使用頻率最低)
	優勢在於第三張表完全自定義擴充套件性高,劣勢在於無法使用外來鍵方法和正反向;
	class Book(models.Model):
    	    title = models.CharField(max_length=32)
  	class Author(models.Model):
    	    name = models.CharField(max_length=32)
  	class Book2Author(models.Model):
    	    book_id = models.ForeignKey(to='Book')
    	    author_id = models.ForeignKey(to='Author')
# 半自動(常見)
	正反向還可以使用,並且第三張表可以擴充套件,唯一的缺陷是不能用:add\set\remove\clear四個方法
  
	class Book(models.Model):
            title = models.CharField(max_length=32)
            authors = models.ManyToManyField(
                            to='Author',
                            through='Book2Author',  # 指定表
                            through_fields=('book','author')  # 指定欄位
            )
  	class Author(models.Model):
            name = models.CharField(max_length=32)
        '''多對多建在任意一方都可以,如果建在作者表,欄位順序互換即可'''
            books = models.ManyToManyField(
                            to='Author',
                            through='Book2Author',  # 指定表
                            through_fields=('author','book')  # 指定欄位
            )
  	class Book2Author(models.Model):
            book = models.ForeignKey(to='Book')
            author = models.ForeignKey(to='Author')