多表操做

1、數據庫表關係

一、單表操做:php

Book
    id     title     price     publish     email    addr
    1       php       100     人民出版社     111      北京
    2       python    200     沙河出版社     222      沙河
    3       go        100     人民出版社     111      北京
    4       java      300     人民出版社     111      北京

  總結:重複內容過多,浪費大量存儲空間,資源浪費。html

二、表關係之一對多:java

Book
    id     title     price     publish_id
    1       php       100          1
    2       python    200          1
    3       go        100          2
    4       java      300          1

Pulish
    id      name      email       addr
    1     人民出版社     111        北京
    2     沙河出版社     222        沙河
    
    一個出版社能夠對應多本書,可是一本書對應不了多個出版社。

  總結:一旦肯定表關係是一對多時,在多對應的表中建立關聯字段。python

三、表關係之多對多:mysql

Book
    id     title     price     publish_id
    1       php       100          1
    2       python    200          1
    3       go        100          2
    4       java      300          1

Author
    id      name      age       addr
    1       alex      34      beijing
    2       egon      55      nanjing

Book2Author
    id      book_id      author_id
    1           2             1
    2           2             2
    3           3             2

  總結:一旦肯定表關係是多對多,建立第三張關係表:id 和 另外兩個表的關聯字段。git

# alex出版過的書籍名稱(子查詢)
select id from Author where name='alex';
select book_id from Book2Author where author_id=1;
select title from Book where id = book_id;

四、表關係之一對一sql

Author
    id   name    age     ad_id(UNIQUE)
    1    alex     34        1
    2    egon     55        2

AuthorDetail
    id     addr     gender    tel    gf_name     author_id(UNIQUE)
    1    beijing     male     110     小花            1
    2    nanjing     male     911     槓娘            2

  總結:一旦肯定是一對一關係,在兩張表中的任意一張表中創建關聯字段+ UNIQUE。數據庫

五、表關係之關聯字段和外鍵約束django

  建立關聯字段和約束不是必然關係,可是不創建約束的話,從引擎的角度來講兩個表之間沒有任何關聯,所以刪除的時候,再查找時會找不到數據。python3.x

  建立關聯字段是爲了進行查詢,創建約束是爲了防止出現髒數據。

六、表關係之sql建立關聯表

GREATE TABLE publish(
                id INT PRIMARY KEY auto_increment,
                name VARCHAR (20)
                );

GREATE TABLE book(
                id INT PRIMARY KEY auto_increment,
                title VARCHAR (20),
                price DECIMAL (8,2),
                pub_date DATE,
                publish_id INT,   # 關聯字段
                FOREIGN KEY (publish_id) REFERENCES publish(id)    # 關聯字段約束
                );

GREATE TABLE authordetail (
                id INT PRIMARY KEY auto_increment,
                tel VARCHAR (20)    
                );

GREATE TABLE author (
                id INT PRIMARY KEY auto_increment,
                name VARCHAR (20),
                age INT,
                authordetail_id INT UNIQUE,    # 一對一約束
                FOREIGN KEY (authordetail_id) REFERENCES authordetail(id)
                );
 
GREATE TABLE book2author (   # 多對多
                id INT PRIMARY KEY auto_increment,
                book_id INT,
                author_id INT,
                FOREIGN KEY (book_id) REFERENCES book(id),
                FOREIGN KEY (author_id) REFERENCES author(id)
                ); 

2、建立模型

  實例:咱們來假定下面這些概念,字段和關係

  做者模型:一個做者有姓名和年齡。

  做者詳細模型:把做者的詳情放到詳情表,包含生日,手機號,家庭住址等信息。做者詳情模型和做者模型之間是一對一的關係(one-to-one)

  出版商模型:出版商有名稱,所在城市以及email。

  書籍模型: 書籍有書名和出版日期,一本書可能會有多個做者,一個做者也能夠寫多本書,因此做者和書籍的關係就是多對多的關聯關係(many-to-many);一本書只應該由一個出版商出版,因此出版商和書籍是一對多關聯關係(one-to-many)。

一、模型創建以下:

from django.db import models

# 出版社表
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()


# 做者詳情表
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday=models.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)


class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()

    # 與AuthorDetail創建一對一的關係,一對一的關係創建在任意一邊均可以
    # to="AuthorDetail",加了引號以後是在全局中尋找不會由於位置關係找不到AuthorDetail
    authorDetail=models.OneToOneField(to="AuthorDetail", to_field="nid", on_delete=models.CASCADE)
    """  上面語句表明含義爲下面sql語句
    authordetail_id INT UNIQUE,    # 一對一約束
    FOREIGN KEY (authordetail_id) REFERENCES authordetail(id)
    """


class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)

    #與publish創建  一對多關係  ,外鍵字段創建在多的一方
    publish=models.ForeignKey(to="Publish", to_field="nid",on_delete=models.CASCADE)
    """  上面語句表明含義爲下面的sql語句
    publish_id INT,   # 關聯字段
    FOREIGN KEY (publish_id) REFERENCES publish(id)    # 關聯字段約束
    """

    # 多對多  與Author表創建多對多的關係,ManyToManyField能夠建在兩個模型中的任意一個,自動建立第三張表
    authors = models.ManyToManyField(to="Author")
    """  上面這個語句含義爲下面的sql語句
    GREATE TABLE book2author ( 
                id INT PRIMARY KEY auto_increment,
                book_id INT,
                author_id INT,
                FOREIGN KEY (book_id) REFERENCES book(id),
                FOREIGN KEY (author_id) REFERENCES author(id)
                ); 
    """


# 這種多對多寫法能夠由  authors = models.ManyToManyField(to="Author") 替代
# class Book2Author(models.Model):
#     nid = models.AutoField(primary_key=True)
#     book = models.ForeignKey(to="Book")
#     author = models.ForeignKey(to="Author")

二、參數解析:

ForeignKey一對多多對一

ManyToManyField多對多

OneToOneField一對一

 

ForeignKey.to_field:指定當前關係與被關聯對象中的哪一個字段關聯。默認狀況下,to_field 指向被關聯對象的主鍵。

ForeignKey.on_delete:當一個model對象的ForeignKey關聯的對象被刪除時,默認狀況下此對象也會一塊兒被級聯刪除的。

CASCADE:默認值,model對象會和ForeignKey關聯對象一塊兒被刪除。

三、數據庫配置

(1)配置mysql數據庫鏈接(可選)

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME':'orm2',   # 要鏈接的數據庫,鏈接前須要建立好
        'USER':'root',  # 鏈接數據庫的用戶名
        'PASSWORD':'1234',  # 鏈接數據庫的密碼
        'HOST':'127.0.0.1',     # 鏈接主機,默認本級
        'PORT': 3306,     #  端口 默認3306
    }
}

(2)使用PyMysql鏈接mysql數據庫實例(可選)

  在Python3.x之後,因爲Python統一了數據庫鏈接的接口,開始使用pymysql,pymysql和MySQLdb 在使用方式上是相似的: 

  所以,python3.x連接mysql數據庫應安裝Python,使用pip安裝方法是:

pip install PyMySQL

  在django項目中配置ORM2/__init__.py文件:

import pymysql
pymysql.install_as_MySQLdb()

(3)配置logging查看翻譯成sql語句

  在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',
        },
    }
}  

  

四、數據遷移

  經過兩條數據庫遷移命令便可在指定的數據庫中建立表:

$ python3 manage.py makemigrations
$ python3 manage.py migrate

  生成以下表:  

  

  

  

  

  

五、注意事項

  • 表的名稱myapp_modelName,是根據 模型中的元數據自動生成的,也能夠覆寫爲別的名稱
  • id 字段是自動添加的(沒有設置id時會自動添加id字段)
  • 對於外鍵字段,Django 會在字段名上添加"_id" 來建立數據庫中的列名
  • 這個例子中的CREATE TABLE SQL 語句使用PostgreSQL 語法格式,要注意的是Django 會根據settings 中指定的數據庫類型來使用相應的SQL 語句。
  • 定義好模型以後,你須要告訴Django _使用_這些模型。你要作的就是修改配置文件中的INSTALL_APPSZ中設置,在其中添加models.py所在應用的名稱。(見前面settings設置)
  • 外鍵字段 ForeignKey 有一個 null=True 的設置(它容許外鍵接受空值 NULL),你能夠賦給它空值 None 。

六、字段選項

  每一個字段有一些特有的參數,例如,CharField須要max_length參數來指定VARCHAR數據庫字段的大小。

  還有一些適用於全部字段的通用參數。這些參數在文檔中有詳細定義,經常使用參數以下所示:

 

(1)null

若是爲True,Django 將用NULL 來在數據庫中存儲空值。 默認值是 False.

(1)blank

若是爲True,該字段容許不填。默認爲False。
要注意,這與 null 不一樣。null純粹是數據庫範疇的,而 blank 是數據驗證範疇的。
若是一個字段的blank=True,表單的驗證將容許該字段是空值。若是字段的blank=False,該字段就是必填的。

(2)default

字段的默認值。能夠是一個值或者可調用對象。若是可調用 ,每有新對象被建立它都會被調用。

(3)primary_key

若是爲True,那麼這個字段就是模型的主鍵。若是你沒有指定任何一個字段的primary_key=True,
Django 就會自動添加一個IntegerField字段作爲主鍵,因此除非你想覆蓋默認的主鍵行爲,
不然不必設置任何一個字段的primary_key=True。

(4)unique

若是該值設置爲 True, 這個數據字段的值在整張表中必須是惟一的

(5)choices
由二元組組成的一個可迭代對象(例如,列表或元組),用來給字段提供選擇項。 若是設置了choices ,默認的表單將是一個選擇框而不是標準的文本框,並且這個選擇框的選項就是choices 中的選項。

這是一個關於 choices 列表的例子:

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)
每一個元組中的第一個元素,是存儲在數據庫中的值;第二個元素是在管理界面或 ModelChoiceField 中用做顯示的內容。 在一個給定的 model 類的實例中,想獲得某個 choices 字段的顯示值,就調用 get_FOO_display 方法(這裏的 FOO 就是 choices 字段的名稱 )。例如:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)


>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'  

  更多詳見:字段選項(Field options)

3、添加表記錄

操做前先簡單錄入一些數據:

author表:

  

authordetail表:

  

一、添加普通字段(示例)

# 方式1
publish_obj=Publish(name="人民出版社",city="北京",email="renMin@163.com")
publish_obj.save() # 將數據保存到數據庫

# 方式2 
返回值publish_obj是添加的記錄對象
publish_obj=Publish.objects.create(name="人民出版社",city="北京",email="renMin@163.com")

# 方式3
表.objects.create(**request.POST.dict())

二、添加一對多關係(外鍵字段)

# 方式1:
# 爲book表綁定出版社:一對多綁定關係  book  publish
book_obj = Book.objects.create(title="西遊記", price=100, publishDate="2012-12-1", publish_id=1)
print(book_obj.title)   # 返回值是添加的書籍對象屬性

# 方式2:
pub_obj = Publish.objects.filter(nid=1).first()

book_obj = Book.objects.create(title="紅樓夢", price=100, publishDate="2012-12-1", publish=pub_obj)
print(book_obj.title)     # 紅樓夢
print(book_obj.price)     # 100
print(book_obj.publishDate)   # 2012-12-1
print(book_obj.publish)   # 與這本書關聯的出版社對象:Publish object (1),設置__str__後,打印 人民出版社


# 查詢西遊記出版社對應的郵箱
book_obj = Book.objects.filter(title="西遊記").first()
print(book_obj.publish.email)     # 123@qq.com

  book表:

  

核心:book_obj.publish與book_obj.publish_id是什麼?

book_obj.publish是與這本書關聯的出版社對象,book_obj.publish_id是出版社id號。

三、添加多對多關係

# 書籍和做者  多對多關係
# 當前生成的書籍對象
book_obj = Book.objects.create(title="金瓶*梅", price=100, publishDate="2012-12-1", publish_id=1)
# 添加普通字段:書籍綁定的做者對象
egon = Author.objects.get(name="egon", age=23, authorDetail_id=2)
alex = Author.objects.get(name="alex", age=33, authorDetail_id=1)

# 綁定多對多關係的API接口,即向關係表book_authors中添加記錄
book_obj.authors.add(egon, alex)  #  將某個特定的 model 對象添加到被關聯對象集合中。   =======    book_obj.authors.add(*[])

# 另外一種寫法是寫入author的主鍵值
# book_obj.authors.add(1,2,3)
# 另外一種寫法是傳入一個列表,*是函數傳列表的時候若是等效位置參數的時候須要加一個*號
# book_obj.authors.add(*[1,2,3])

#建立並保存一個新對象,而後將這個對象加被關聯對象的集合中,而後返回這個新對象。
book_obj.authors.create()

(1)數據庫表紀錄生成以下

book表:

  

book_author表:

  

(2)核心:book_obj.authors.all()是什麼?

book = Book.objects.filter(nid=6).first()
print(book.authors.all())    # <QuerySet [<Author: alex>]>     與這本書關聯的全部做者對象的集合

   答案:與這本書關聯的全部做者對象的集合。queryset數據類型,列表裏面放着的都是這本書關聯做者對象。

(3)如何拿到書籍全部做者的名字?

# 查詢ID爲6的書籍的全部做者的名字
ret = book.authors.all().values("name")
print(ret)     # <QuerySet [{'name': 'alex'}]>

  經過book.authors.all().values就能夠取到書籍對應做者的屬性。

(4)解除多對多關係——將某個特定的對象從被關聯對象集合中去除

# 解除多對多關係
book = Book.objects.filter(nid=6).first()

book.authors.remove(2)  # 將book_authors中對應的6-2這條記錄刪除
# 相似的寫法還有:
# book.authors.remove(*[1,2])

  book_authors表:

  

(5)解除多對多關係——清空被關聯對象集合

  book_obj.authors.clear() 

(6)先清空再設置

  book_obj.authors.set()

四、關聯管理器(RelatedManager)

  "關聯管理器"是在一對多或者多對多關聯上下文中使用的管理器。它存在於下面兩種狀況:

(1)ForeignKey關係的「另外一邊」。像這樣:

from django.db import models
 
class Reporter(models.Model):
    # ...
    pass
 
class Article(models.Model):
    reporter = models.ForeignKey(Reporter)

  在上面的例子中,管理器reporter.article_set擁有下面的方法。

(2)ManyToManyField關係的兩邊:

class Topping(models.Model):
    # ...
    pass
 
class Pizza(models.Model):
    toppings = models.ManyToManyField(Topping)

  這個例子中,topping.pizza_set 和pizza.toppings都擁有下面的方法。

五、關聯管理器擁有的方法

add(obj1[, obj2, ...]):把指定的模型對象添加到關聯對象集中。

把指定的模型對象添加到關聯對象集中。

例如:

>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.add(e) # Associates Entry e with Blog b.
在上面的例子中,對於ForeignKey關係,e.save()由關聯管理器調用,執行更新操做。然而,在多對多關係中使用add()並不會調用任何 save()方法,而是由QuerySet.bulk_create()建立關係。

延伸:

# 1 *[]的使用
>>> book_obj = Book.objects.get(id=1)
>>> author_list = Author.objects.filter(id__gt=2)
>>> book_obj.authors.add(*author_list)


# 2 直接綁定主鍵
book_obj.authors.add(*[1,3])  # 將id=1和id=3的做者對象添加到這本書的做者集合中
                              # 應用: 添加或者編輯時,提交做者信息時能夠用到.

create(**kwargs):建立一個新的對象,保存對象,並將它添加到關聯對象集之中。返回新建立的對象。

建立一個新的對象,保存對象,並將它添加到關聯對象集之中。返回新建立的對象:

>>> b = Blog.objects.get(id=1)
>>> e = b.entry_set.create(
...     headline='Hello',
...     body_text='Hi',
...     pub_date=datetime.date(2005, 1, 1)
... )

# No need to call e.save() at this point -- it's already been saved.
這徹底等價於(不過更加簡潔於):

>>> b = Blog.objects.get(id=1)
>>> e = Entry(
...     blog=b,
...     headline='Hello',
...     body_text='Hi',
...     pub_date=datetime.date(2005, 1, 1)
... )
>>> e.save(force_insert=True)
要注意咱們並不須要指定模型中用於定義關係的關鍵詞參數。在上面的例子中,咱們並無傳入blog參數給create()。Django會明白新的 Entry對象blog 應該添加到b中。

remove(obj1[, obj2, ...]):從關聯對象集中移除執行的模型對象

從關聯對象集中移除執行的模型對象:

>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.remove(e) # Disassociates Entry e from Blog b.
對於ForeignKey對象,這個方法僅在null=True時存在。

clear():從關聯對象集中移除一切對象

從關聯對象集中移除一切對象。

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.clear()
注意這樣不會刪除對象 —— 只會刪除他們之間的關聯。

就像 remove() 方法同樣,clear()只能在 null=True的ForeignKey上被調用。

set()方法:先清空,再設置

def change_book(request,edit_book_id):
    edit_book_obj = Book.objects.filter(pk=edit_book_id).first()

    if request.method=="POST":
        title = request.POST.get("title")
        price = request.POST.get("price")
        pub_date = request.POST.get("pub_date")
        publish_id = request.POST.get("publish_id")
        authors_id_list = request.POST.getlist("authors_id_list")  # checkbox,select傳多個值的時候用getlist()
        # 更新爲修改的新內容
        Book.objects.filter(pk=edit_book_id).update(title=title,price=price,publishDate=pub_date,publish_id=publish_id)

        # 更新書的做者(覆蓋原記錄)
        # 方法一:
        # edit_book_obj.authors.clear()
        # edit_book_obj.authors.add(*authors_id_list)

        # 方法二:set()方法先清空再設置
        edit_book_obj.authors.set(authors_id_list)

        return redirect("/books/")

    publish_list = Publish.objects.all()
    author_list = Author.objects.all()

    return render(request, "editbook.html", {"edit_book_obj": edit_book_obj,"publish_list":publish_list, "author_list":author_list})

方法使用注意

  對於全部類型的關聯字段,add()、create()、remove()和clear(),set()都會立刻更新數據庫。換句話說,在關聯的任何一端,都不須要再調用save()方法。

  直接賦值:

  經過賦值一個新的可迭代的對象,關聯對象集能夠被總體替換掉。

>>> new_list = [obj1, obj2, obj3]
>>> e.related_set = new_list

  若是外鍵關係知足null=True,關聯管理器會在添加new_list中的內容以前,首先調用clear()方法來解除關聯集中一切已存在對象的關聯。不然, new_list中的對象會在已存在的關聯的基礎上被添加。

4、基於對象的跨表查詢(子查詢)

  子查詢概念:子查詢是將一個查詢語句嵌套在另外一個查詢語句中。

# 一對多查詢的正向查詢:查詢金瓶*梅這本書的出版社的名字
book_obj = Book.objects.filter(title="金瓶*梅").first()
print(book_obj.publish)       # 與這本書關聯出版社對象
print(book_obj.publish.name)

# 等同於下列sql語句形式
# select publish_id from Book where title = "金瓶*梅";
# select name from Publish where id = (select publish_id from Book where title = "金瓶*梅");

一、一對多查詢(publish與book)

正向查詢:按字段 

# 一對多查詢的正向查詢:查詢金瓶*梅這本書的出版社的名字
book_obj = Book.objects.filter(title="金瓶*梅").first()
print(book_obj.publish)       # 與這本書關聯出版社對象
print(book_obj.publish.name)

反向查詢:按表名小寫_set.all()

# 一對多查詢的反向查詢:查詢人民出版社出版過的書籍名稱
publish_obj = Publish.objects.filter(name="人民出版社").first()   # 出版社對象
ret = publish_obj.book_set.all()
print(ret)     # <QuerySet [<Book: 紅樓夢>, <Book: 西遊記>, <Book: 金瓶*梅>]>

總結:

  (1)假如A、B兩個表有關係,關聯屬性在A表中。正向查詢——A去查B對象;反向查詢——B去查A對象。

  (2)Book(關聯屬性:publish) ,用book去找關聯對象出版社Publish:book_obj.publish;

  (3)用publish去找關聯對象書籍book:publish_obj.book_set.all() ,且數據類型是queryset。

二、一對一查詢(author 與 authordetail)

正向查詢:按字段

# 一對一查詢的正向查詢:查詢alex的手機號
alex = Author.objects.filter(name="alex").first()
print(alex.authordetail.telephone)  

反向查詢:按表名小寫 

# 一對一查詢的反向查詢:查詢手機號爲110的做者名字和年齡
ad = AuthorDetail.objects.filter(telephone="110").first()
print(ad.author.name)
print(ad.author.age)

三、多對多查詢(author 與 book)

正向查詢(按字段:authors):

# 金瓶眉全部做者的名字以及手機號
 
book_obj=Book.objects.filter(title="金瓶眉").first()
authors=book_obj.authors.all()
for author_obj in authors:
     print(author_obj.name,author_obj.authorDetail.telephone)

反向查詢(按表名:book_set):

# 查詢egon出過的全部書籍的名字

author_obj=Author.objects.get(name="egon")
book_list=author_obj.book_set.all()        #與egon做者相關的全部書籍
for book_obj in book_list:
    print(book_obj.title)

注意:

  能夠經過在 ForeignKey() 和ManyToManyField的定義中設置 related_name 的值來覆寫 FOO_set 的名稱。例如,若是 Article model 中作一下更改:

publish = ForeignKey(Book, related_name='bookList')

  那麼接下來就會如咱們看到這般:

# 查詢 人民出版社出版過的全部書籍
 
publish=Publish.objects.get(name="人民出版社")
book_list=publish.bookList.all()  # 與人民出版社關聯的全部書籍對象集合

5、基於雙下劃線的跨表查詢(join查詢)

  Django 還提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關係,它能自動確認 SQL JOIN 聯繫。要作跨關係查詢,就使用兩個下劃線來連接模型(model)間關聯字段的名稱,直到最終連接到你想要的model 爲止。 

  正向查詢按字段,反向查詢按表名小寫用來告訴ORM引擎join哪張表。

一、一對多查詢

# 一對多查詢:查詢金瓶*梅這本書的出版社的名字
"""
SELECT 
  app01_publish.name
FROM 
  app01_book 
Inner join 
  app01_publish
ON app01_book.publish_id = app01_publish.nid
WHERE app01_book.title='金瓶*梅';
"""
# 方式一:正向查詢按字段
# ret = Book.objects.filter(title="金瓶*梅").values("publish__name")    # 正向查詢按字段
# print(ret)      # <QuerySet [{'publish__name': '人民出版社'}]>

# 方式二:反向查詢按表名小寫
# SELECT app01_publish.name FROM app01_publish INNER JOIN app01_book....僅僅是順序不一樣
ret = Publish.objects.filter(book__title="金瓶*梅").values("name")      # 反向查詢按表名小寫
# publish去找book所以是反向查詢,經過表名小寫的方式通知ORM引擎去join book表,而後使用book的屬性title
print(ret)

  總結:正向查詢按字段,反向查詢按表名小寫用來告訴ORM引擎join哪張表。

二、多對多查詢

  value等同於sql語句中的select,filter等同於sql語句中的where。

# 多對多查詢:查詢金瓶*梅這本書全部做者的名字
"""
SELECT app01_author.name FROM
  app01_book
INNER JOIN
  app01_book_authors
ON app01_book.nid = app01_book_authors.book_id
INNER JOIN
  app01_author
ON app01_book_authors.author_id = app01_author.nid
WHERE app01_book.title="金瓶*梅";
"""
# 方式一:
# 需求:經過Book表join與其關聯的Author表,屬於正向查詢:按字段Book模型中字段authors = models.ManyToManyField(to="Author"),來通知ORM引擎join book_authors 和 author
ret = Book.objects.filter(title="金瓶*梅").values("authors__name")
print(ret)   # <QuerySet [{'authors__name': 'alex'}]>

# 方式二:
# 需求:經過Author表join與其關聯的Book表,屬於反向查詢:按表名小寫book通知ORM引擎join book_authors與book表
ret = Author.objects.filter(book__title="金瓶*梅").values("name")
print(ret)    # <QuerySet [{'name': 'alex'}]>

三、一對一查詢

# 一對一查詢:查詢alex的手機號
# 方式一:
# 需求:經過Author表join與其關聯的AuthorDetail表,屬於正向查詢:按Author表中字段authorDetail通知ORM引擎join Authordetail表
ret = Author.objects.filter(name="alex").values("authorDetail__telephone")
print(ret)      # <QuerySet [{'authorDetail__telephone': 110}]>

# 方式二:
# 需求:經過AuthorDetail表join與其關聯的Author表,屬於反向查詢:按表名小寫author通知ORM引擎join Author表
ret = AuthorDetail.objects.filter(author__name="alex").values("telephone")
print(ret)      # <QuerySet [{'telephone': 110}]>

四、進階練習(連續跨表)

# 練習:手機號以110開頭的做者出版過得全部書籍名稱及書籍出版社名稱
# 方式一:
# 需求:經過Book表join AuthorDetail表,Book與AuthorDetail無關聯,所以必須連續跨表
# book,book_authors,author,authordetail,publish五張表join
ret = Book.objects.filter(authors__authorDetail__telephone__startswith="110").values("title", "publish")
print(ret)      # <QuerySet [{'title': '金瓶*梅', 'publish': 1}]>

# 方式二:
# 需求:以做者爲基表,Author表join與其關聯的authorDetail,屬於正向查詢,找到authorDetail下telephone屬性,查看110開頭的電話的做者。
ret = Author.objects.filter(authorDetail__telephone__startwith="110").values("book__title", "book__publish__name")
print(ret)

五、related_name

  反向查詢時,若是定義了related_name ,則用related_name替換表名,例如: 

publish = ForeignKey(Blog, related_name='bookList')

  練習: 查詢人民出版社出版過的全部書籍的名字與價格(一對多)

# 反向查詢 再也不按表名:book,而是related_name:bookList
    queryResult=Publish.objects
              .filter(name="人民出版社")
              .values_list("bookList__title","bookList__price") 

 6、聚合查詢和分組查詢

  先了解sql中的聚合與分組概念。

一、聚合:aggregate(*args, **kwargs)

# 查詢全部書籍的平均價格
from django.db.models import Avg,Max,Min,Count

ret = Book.objects.all().aggregate(Avg("price"))
print(ret)   # {'price__avg': 100.0}   返回值是一個字典,鍵自動由字段和聚合函數拼接組成

  aggregate()QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典聚合 aggregate:返回值是一個字典,再也不是一個queryset。

  鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。

若是你想要爲聚合值指定一個名稱,能夠向聚合子句提供它:

# 自定義鍵:
ret = Book.objects.all().aggregate(avg_price=Avg("price"))
print(ret)   # {'avg_price': 100.0}

若是你但願生成不止一個聚合,你能夠向aggregate()子句中添加另外一個參數。因此,若是你也想知道全部圖書價格的最大值和最小值,能夠這樣查詢:

ret = Book.objects.all().aggregate(avg_price=Avg("price"),max_price=Max("price"))
print(ret)   # {'avg_price': 100.0, 'max_price': Decimal('100.00')}

二、分組:annotate()

  爲調用的QuerySet中每個對象都生成一個獨立的統計值(統計方法用聚合函數)。

  前置準備建立Emp表:

class Emp(models.Model):
    name=models.CharField(max_length=32)
    age=models.IntegerField()
    salary=models.DecimalField(max_digits=8,decimal_places=2)
    dep=models.CharField(max_length=32)
    province=models.CharField(max_length=32)

  添加數據以下:

(1)單表分組查詢

  單表分組查詢的ORM語法:單表模型.objects.values("group by的字段").annotate(聚合函數("統計字段"))

# --------------------------->分組查詢 annotate:返回值依然是queryset
# 單表分組查詢
# 示例一:查詢每一個部門的名稱及員工平均薪水
# select dep,Avg(salary) from emp group by dep;
ret = Emp.objects.values("dep").annotate(avg_salary=Avg('salary'))
print(ret)    # <QuerySet [{'dep': '教學部', 'avg_salary': 51000.0}, {'dep': '保安部', 'avg_salary': 5000.0}]>

# 單表分組查詢的ORM語法:單表模型.objects.values("group by的字段").annotate(聚合函數("統計字段"))

# 實例二:查詢每一個省份的名稱和員工數
ret = Emp.objects.values("province").annotate(c=Count("id"))
print(ret)    # <QuerySet [{'province': '山東省', 'c': 2}, {'province': '河北省', 'c': 1}]>

  注意:在單表分組下,按照主鍵進行group by 是沒有任何意義的。

# annotate是按照前面顯示的字段group by。
ret = Emp.objects.all()   # select * from emp;
print(ret)

ret = Emp.objects.values("name")    # select name from emp;
print(ret)

# 下面表明的是對全部字段進行group by
Emp.objects.all().annotate(avg_salary=Avg("salary"))

 (2)多表分組查詢

  多表分組查詢示例:

 Book表
        id   title    date      price  publish_id
        1	紅樓夢	2012-12-12	101	   1
        2	西遊記	2012-12-12	101	   1
        3	三國演繹	2012-12-12	101	   1
        4	金瓶*梅	2012-12-12	301	   2

Publish表
        id    name      addr   email
        1	人民出版社	北京	   123@qq.com
        2	南京出版社	南京	   345@163.com


1 查詢每個出版社出版的書籍個數
Book.objects.values("publish_id").annotate(Count("id"))   # 單表查詢便可完成

2 示例 查詢每個出版社的名稱以及出版的書籍個數
    join sql : select * from Book inner join Publish on book.publish_id=publish.id

兩張表合併爲一張大表:
   id   title     date         price    publish_id   publish.id     publish.name  publish.addr     publish.email
    1   紅樓夢	2012-12-12	101	   1            1	       人民出版社	     北京	     123@qq.com
    2   西遊記	2012-12-12	101	   1            1	       人民出版社	     北京	     123@qq.com
    3   三國演繹	2012-12-12	101	   1            1	       人民出版社	     北京	     123@qq.com
    4   金瓶*梅	2012-12-12	301	   2            2	       南京出版社	     南京	     345@163.com


 分組查詢sql:
   select publish.name,Count("title") from Book inner join Publish on book.publish_id=publish.id 
                             group by publish.id;

(3)思考:如何用ORM語法進行跨表分組查詢?

  在單表分組下,按照主鍵進行group by 是沒有任何意義的;可是在多表下對主鍵進行group by是有意義的。

# 查詢每個出版社的名稱以及出版的書籍個數
ret = Publish.objects.values("nid").annotate(c=Count("book__title"))  # 與單表分組查詢的區別就是統計的字段跨表了
print(ret)   # <QuerySet [{'nid': 1, 'c': 2}, {'nid': 2, 'c': 1}, {'nid': 3, 'c': 0}]>

# 方式一:因爲nid不是符合需求,改用name分組:
ret = Publish.objects.values("name").annotate(c=Count("book__title"))  # 與單表分組查詢的區別就是統計的字段跨表了
print(ret)    # <QuerySet [{'name': '人民出版社', 'c': 2}, {'name': '蘋果出版社', 'c': 1}, {'name': '橘子出版社', 'c': 0}]>

# 方式二:用nid分組,用values去取,也能查詢到名稱信息
ret = Publish.objects.values("nid").annotate(c=Count("book__title")).values("name","c")
print(ret)    # <QuerySet [{'name': '人民出版社', 'c': 2}, {'name': '蘋果出版社', 'c': 1}, {'name': '橘子出版社', 'c': 0}]>

  用一個新例子幫助理解:

# 查詢每個做者的名字以及出版過得書籍的最高價格
"""
SELECT app01_author.name,Max(app01_book.price) FROM app01_book
INNER JOIN app01_book_authors ON app01_book.nid = app01_book_authors.book_id
INNER JOIN app01_author ON app01_author.nid=app01_book_authors.author_id
GROUP BY app01_author.nid;
"""
# pk能夠指代id或nid,join book表反向查詢
ret = Author.objects.values("pk").annotate(max_price=Max("book__price")).values("name","max_price")
print(ret)  # <QuerySet [{'name': 'alex', 'max_price': Decimal('100.00')}, {'name': 'egon', 'max_price': None}]>

  另外一個示例:

# 示例:查詢查詢每個書籍的名稱以及對應的做者個數
"""
SELECT app01_book.title,COUNT(app01_author.name) FROM app01_book
INNER JOIN app01_book_authors ON app01_book.nid = app01_book_authors.book_id
INNER JOIN app01_author ON app01_author.nid=app01_book_authors.author_id
GROUP BY app01_book.nid;
"""
ret=Book.objects.values("pk").annotate(c=Count("authors__name")).values("title","c")
print(ret)  # <QuerySet [{'title': '金瓶*梅', 'c': 1}, {'title': '紅樓夢', 'c': 0}, {'title': '西遊記', 'c': 0}]>

  跨表分組查詢另外一種寫法:

# 每個後的表模型.objects.annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段")
# 示例:查詢每個出版社的名稱及出版社書籍個數
ret = Publish.objects.all().annotate(c=Count("book__title")).values("name","email","c")
# 省略all()的簡略寫法
ret = Publish.objects.annotate(c=Count("book__title")).values("name", "email", "c")
print(ret)   # <QuerySet [{'name': '人民出版社', 'email': '123@qq.com', 'c': 2}, {'name': '蘋果出版社', 'email': 'yuan@163.com', 'c': 1}, {'name': '橘子出版社', 'email': 'egon@qq.com', 'c': 0}]>

  總結跨表的分組查詢的模型:

每個後的表模型.objects.values("pk").annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段")

每個後的表模型.objects.annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段")

(4)查詢練習

# 練習1:統計每個出版社的最便宜的書
# 模板:每個後的表模型.objects.annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段")
publishList = Publish.objects.annotate(MinPrice=Min("book__price"))
for publish_obj in publishList:
    print(publish_obj.name,publish_obj.MinPrice)
"""
人民出版社 100.00
蘋果出版社 100.00
橘子出版社 None
"""
# annotate的返回值是querySet,若是不想遍歷對象,能夠用上valuelist:
queryResult = Publish.objects.annotate(MinPrice=Min("book__price")).values_list("name", "MinPrice")
print(queryResult)  # <QuerySet [('人民出版社', Decimal('100.00')), ('蘋果出版社', Decimal('100.00')), ('橘子出版社', None)]>


# 練習3:統計每一本以py開頭的書籍的做者個數
# 每個後的表模型.objects.values("pk").annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段")
ret = Book.objects.filter(title__startswith="py").values("pk").annotate(c=Count("authors__name")).values("title","c")
print(ret)   # <QuerySet []>

# 練習4:統計不止一個做者的圖書  gt:大於
ret = Book.objects.values("pk").annotate(c=Count("authors__name")).filter(c__gt=1).values("title", "c")
print(ret)   # <QuerySet []>

# 練習5:根據一本圖書做者數量的多少對查詢集queryset進行排序
Book.objects.annotate(num_authors=Count("authors")).order_by("num_authors")

# 練習6:查詢各個做者出的書的總價格
# 按author表的全部字段group by
from django.db.models import Sum
queryResult = Author.objects.annotate(sum_price=Sum("book__price")).values_list("name", "sum_price")
print(queryResult)   # <QuerySet [('alex', Decimal('100.00')), ('egon', None)]>

 7、F查詢和Q查詢

一、F查詢

  Django 提供 F() 來對兩個字段的值作比較比較。F() 的實例能夠在查詢中引用字段,來比較同一個 model 實例中兩個不一樣字段的值。

修改models.py內的book類,添加評論數和已讀數:

class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # 臨時更改表結構:
    # read_num = models.IntegerField()
    # comment_num = models.IntegerField()
    """  因爲臨時更改沒有添加默認值,會提示以下報錯:
    Please select a fix:
    1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
    2) Quit, and let me add a default in models.py
    """
    read_num = models.IntegerField(default=0)
    comment_num = models.IntegerField(default=0)

而後在book表內添加修改評論數和已讀數:

from django.db.models import F
# 臨時更改表結構後,須要查看書籍評論次數大於已讀次數
ret = Book.objects.filter(comment_num__gt=F("read_num"))
print(ret)   # <QuerySet [<Book: 紅樓夢>, <Book: 金瓶*梅>]>

Django 還支持 F() 對象之間以及 F() 對象和常數之間的加減乘除和取模的操做。

# 查詢已讀數大於評論數2倍的書籍
ret = Book.objects.filter(read_num__gt=F("comment_num")*2)
print(ret)   # <QuerySet [<Book: 西遊記>]>

修改操做也可使用F函數,好比將每一本書的價格提升10元:

# 全部書籍的價格提高10元
Book.objects.all().update(price=F("price")+10)

二、Q查詢

  filter() 等方法中的關鍵字參數查詢都是一塊兒進行「AND」 的。 若是須要執行更復雜的查詢(例如OR 語句)可使用Q對象

from django.db.models import Q
# 書籍的名稱爲紅樓夢或者書的價格爲110   Q表示或
ret = Book.objects.filter(Q(title="紅樓夢")|Q(price=110))
print(ret)   # <QuerySet [<Book: 紅樓夢>, <Book: 西遊記>, <Book: 金瓶*梅>]>

  Q 對象可使用& 和| 操做符組合起來。當一個操做符在兩個Q 對象上使用時,它產生一個新的Q 對象。等同於下面的語句:

WHERE title ="紅樓夢" OR price > 110

  能夠組合& 和|  操做符以及使用括號進行分組來編寫任意複雜的Q 對象。

  同時,Q 對象可使用~ 操做符取反,這容許組合正常的查詢和取反(NOT) 查詢:

# 書籍名稱不爲紅樓夢或者書的價格爲110   ~表示非
ret=Book.objects.filter(~Q(title="紅樓夢")|Q(price=110))
print(ret)  # <QuerySet [<Book: 紅樓夢>, <Book: 西遊記>, <Book: 金瓶*梅>]>

  查詢函數能夠混合使用Q對象和關鍵字參數。全部提供給查詢函數的參數(關鍵字參數或Q 對象)都將"AND」在一塊兒。可是,若是出現Q 對象,它必須位於全部關鍵字參數的前面。例如:

# 在2012年或2013年出版的名稱包含"紅"
bookList=Book.objects.filter(Q(publishDate__year=2012) | Q(publishDate__year=2014),title__icontains="紅")
print(bookList)    # <QuerySet [<Book: 紅樓夢>]>

  還有一種特別的Q對象用法,查詢條件能夠不用是字段名稱,可使用字符串來完成查詢。

def test(request):
    from django.db.models import Q
    # Q查詢普通寫法:
    ret = Book.objects.all().filter(Q(title="go")|Q(price=103))
    print("ret", ret)    # ret <QuerySet [<Book: go>]>
    # Q查詢特殊用法:
    q = Q()
    q.connectiion = "or"
    q.children.append(("title", "go"))
    q.children.append(("price", 103))
    print("q", q)   # q (AND: ('title', 'yuan'), ('price', 123))
    return HttpResponse(ret, q)   # q (AND: ('title', 'go'), ('price', 103))
相關文章
相關標籤/搜索