第六章:認識 Django ORM

開啟 Django Shell

Django Shell 是 Django 提供類似 IPython 的互動式介面,會對於你輸入的程式碼盡量產生立即的回應。 我們可以透過 Django Shell 來測試所寫的程式是否能夠正確執行,基本上 Django 的行為都可以在這裡想辦法重現。 Django Shell 雖然方便,但是他並不會幫你把輸入的東西輸出出來,所以測試完記得把需要的部分複製回你的程式碼當中。 我們在這邊主要是透過 Django Shell 來學習 Django ORM 的語法。

$ python manage.py shell

import

Django Shell 每次重新開啟時都是全新的狀態,會把記憶體清空,所以請記得每次都要重新進行 import。 這裡主要會使用到 Coffee 的所有 model。 重新進入時,可以透過方向鍵去尋找歷史輸入,忘記輸出時也許有機會找回來,也方便每次重複進行或是做到一半的動作。

>>> from coffees.models import *

>>> 是代表要輸入的部分,用來跟下方的回應做區分,測試時請輸入符號後方的程式碼部分。 上方的 SQL 指令,為教學用途,表示下面的 ORM 等效的 SQL 語法,實際上不會顯示。 只有查詢透過 print(qs.query),可以直接看到查詢的 SQL,這部分下面會提到。 下面是以全新的資料為基底,如果有塞入過資料,可能會有欄位不能重複,或是回應不相同的問題。

新增單筆 (INSERT ONE)

新增產地資料 (為咖啡豆的產地關聯做準備)

基本上新增完會直接回傳新增後對應的 Model 物件。

INSERT INTO "coffees_originplace" ("created_at", "updated_at", "name")
VALUES ('2021-05-01 07:46:34.361803', '2021-05-01 07:46:34.361866', '拉丁美洲');
>>> origin_place = OriginPlace.objects.create(name='拉丁美洲')

>>> origin_place
<OriginPlace: 拉丁美洲>

新增三筆咖啡豆資料

關聯物件要傳入對應的 Model 物件,ORM 會幫自動把關聯物件的 Primary Key 塞入 Foreign Key。

INSERT INTO "coffees_coffee" ("created_at", "updated_at", "name", "description", "roast", "price", "origin_place_id")
VALUES ('2021-05-01 07:47:12.180462', '2021-05-01 07:47:12.180506', '咖啡一號', '一種咖啡豆', 'Medium', 300, 1);
>>> coffee1 = Coffee.objects.create(
    name='咖啡一號',
    description='一種咖啡豆',
    roast='Medium',
    price=300,
    origin_place=origin_place,
)

>>> coffee1
<Coffee: 咖啡一號>

INSERT INTO "coffees_coffee" ("created_at", "updated_at", "name", "description", "roast", "price", "origin_place_id")
VALUES ('2021-05-01 07:47:28.655375', '2021-05-01 07:47:28.655420', '咖啡二號', '一種咖啡豆', 'Medium', 350, 1);
>>> coffee2 = Coffee.objects.create(
    name='咖啡二號',
    description='一種咖啡豆',
    roast='Medium',
    price=350,
    origin_place=origin_place,
)

>>> coffee2
<Coffee: 咖啡二號>

INSERT INTO "coffees_coffee" ("created_at", "updated_at", "name", "description", "roast", "price", "origin_place_id")
VALUES ('2021-05-01 07:47:54.853298', '2021-05-01 07:47:54.853341', '咖啡三號', '這是一種咖啡豆', 'Medium', 400, 1);
>>> coffee3 = Coffee.objects.create(
    name='咖啡三號',
    description='這是一種咖啡豆',
    roast='Medium',
    price=400,
    origin_place=origin_place,
)

>>> coffee3
<Coffee: 咖啡三號>

查詢多筆 (SELECT ALL)

查詢全部資料

SELECT * FROM "coffees_coffee";
>>> qs = Coffee.objects.all()

>>> qs
<QuerySet [<Coffee: 咖啡一號>, <Coffee: 咖啡二號>, <Coffee: 咖啡三號>]>

查看 ORM 真正下的 SQL 語法

只有查詢可以直接透過 print(qs.query) 查看所下的 Query,因為增刪改會直接回傳修改後的資訊或物件。

>>> qs.query
<django.db.models.sql.query.Query object at 0x102f92a90>

>>> print(Coffee.objects.all().query)
SELECT "coffees_coffee"."id",
       "coffees_coffee"."created_at",
       "coffees_coffee"."updated_at",
       "coffees_coffee"."name",
       "coffees_coffee"."description",
       "coffees_coffee"."roast",
       "coffees_coffee"."price",
       "coffees_coffee"."origin_place_id"
FROM "coffees_coffee";

查詢片段資料

QuerySet 可以直接透過切片的語法取得特定的片段。 但 [-1] 這種反向切片用在 QuerySet 上是不合法的。

SELECT * FROM "coffees_coffee" LIMIT 1 OFFSET 2;
>>> Coffee.objects.all()[2]
<Coffee: 咖啡三號>

SELECT * FROM "coffees_coffee" LIMIT 2;
>>> Coffee.objects.all()[:2]
<QuerySet [<Coffee: 咖啡一號>, <Coffee: 咖啡二號>]>

SELECT * FROM "coffees_coffee" LIMIT 1 OFFSET 1;
>>> Coffee.objects.all()[1:2]
<QuerySet [<Coffee: 咖啡二號>]>

查詢單筆 (SELECT ONE)

這幾個在查詢是 空值 (DoesNotExist) 或是 多筆 (MultipleObjectsReturned) 時,都會直接拋錯,請確定一定只有一筆。

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."id" = 1 LIMIT 1;
>>> Coffee.objects.get(pk=1)
<Coffee: 咖啡一號>

SELECT * FROM "coffees_coffee" ORDER BY "coffees_coffee"."id" ASC LIMIT 1;
>>> Coffee.objects.first()
<Coffee: 咖啡一號>

SELECT * FROM "coffees_coffee" ORDER BY "coffees_coffee"."id" DESC LIMIT 1;
>>> Coffee.objects.last()
<Coffee: 咖啡三號>

透過數值欄位過濾 (SELECT WITH INTERGER FIELD)

這裡用價格欄位示範,將欄位加上雙底線再加上運算語法,依序是 =, < (lt), <= (lte), > (gt), >= (gte)

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."price" = 350;
>>> Coffee.objects.filter(price=350)
<QuerySet [<Coffee: 咖啡二號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."price" < 350;
>>> Coffee.objects.filter(price__lt=350)
<QuerySet [<Coffee: 咖啡一號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."price" <= 350;
>>> Coffee.objects.filter(price__lte=350)
<QuerySet [<Coffee: 咖啡一號>, <Coffee: 咖啡二號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."price" > 350;
>>> Coffee.objects.filter(price__gt=350)
<QuerySet [<Coffee: 咖啡三號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."price" >= 350;
>>> Coffee.objects.filter(price__gte=350)
<QuerySet [<Coffee: 咖啡二號>, <Coffee: 咖啡三號>]>

排序 (ORDER_BY)

這裡用價格欄位示範,依序是 由小到大, 由大到小, 亂數排序

SELECT * FROM "coffees_coffee" ORDER BY "coffees_coffee"."price" ASC;
>>> Coffee.objects.order_by('price')
<QuerySet [<Coffee: 咖啡一號>, <Coffee: 咖啡二號>, <Coffee: 咖啡三號>]>

SELECT * FROM "coffees_coffee" ORDER BY "coffees_coffee"."price" DESC;
>>> Coffee.objects.order_by('-price')
<QuerySet [<Coffee: 咖啡三號>, <Coffee: 咖啡二號>, <Coffee: 咖啡一號>]>

SELECT * FROM "coffees_coffee" ORDER BY RAND() ASC;
>>> Coffee.objects.order_by('?')
<QuerySet [<Coffee: 咖啡三號>, <Coffee: 咖啡一號>, <Coffee: 咖啡二號>]>

更新資料 (UPDATE)

這裡示範要修改資料的建立日期,方便我們示範後續的日期過濾跟排序。 我們首先要準備要修改的日期資料。

>>> from django.utils.timezone import datetime
>>> days = [1, 15, 31]
>>> date1, date2, date3 = [datetime(2020, 1, day) for day in days]
>>> date1, date2, date3
(datetime.datetime(2020, 1, 1, 0, 0), datetime.datetime(2020, 1, 15, 0, 0), datetime.datetime(2020, 1, 31, 0, 0))

這裡可以看到 Django ORM 實際上是惰性的,它會要運算時,才真的去執行 SQL。

>>> coffee1.created_at = date1

>>> coffee1.created_at
datetime.datetime(2020, 1, 1, 0, 0)

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."id" = 1 LIMIT 1;
>>> Coffee.objects.get(pk=1).created_at
datetime.datetime(2021, 5, 1, 7, 47, 12, 180462, tzinfo=<UTC>)

UPDATE "coffees_coffee" SET "created_at" = '2019-12-31 16:00:00' WHERE "coffees_coffee"."id" = 1;
>>> coffee1.save()


>>> coffee2.created_at = date2

UPDATE "coffees_coffee" SET "created_at" = '2020-01-14 16:00:00' WHERE "coffees_coffee"."id" = 2;
>>> coffee2.save()


>>> coffee3.created_at = date3

UPDATE "coffees_coffee" SET "created_at" = '2020-01-30 16:00:00' WHERE "coffees_coffee"."id" = 3;
>>> coffee3.save()

透過日期欄位過濾 (SELECT WITH DATETIME FIELD)

這裡用剛剛修改的建立日期欄位示範,一樣將欄位加上雙底線再加上運算語法,依序是 =, < (lt), <= (lte), > (gt), >= (gte)。 我們可以直接用日期字串來比對,而不用建立 datetime 物件。

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."created_at" = '2020-01-14 16:00:00';
>>> Coffee.objects.filter(created_at='2020-01-15')
<QuerySet [<Coffee: 咖啡二號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."created_at" < '2020-01-14 16:00:00';
>>> Coffee.objects.filter(created_at__lt='2020-01-15')
<QuerySet [<Coffee: 咖啡一號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."created_at" <= '2020-01-14 16:00:00';
>>> Coffee.objects.filter(created_at__lte='2020-01-15')
<QuerySet [<Coffee: 咖啡一號>, <Coffee: 咖啡二號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."created_at" > '2020-01-14 16:00:00';
>>> Coffee.objects.filter(created_at__gt='2020-01-15')
<QuerySet [<Coffee: 咖啡三號>]>

SELECT * FROM "coffees_coffee" WHERE "coffees_coffee"."created_at" >= '2020-01-14 16:00:00';
>>> Coffee.objects.filter(created_at__gte='2020-01-15')
<QuerySet [<Coffee: 咖啡二號>, <Coffee: 咖啡三號>]>

查詢多種條件 (SELECT WIHT MULTI CONDITION)

我們除了正向的 filter 來進行過濾外,也可以用反向的 exclude 來進行排除。 我們在下 ORM 時,開頭都會使用 Model.objects,實際上會對應到那個 Model 下的 Manager,由 Manager 來轉換成 ORM。 objects 後面的語法是可以無限串接的,Manager 會幫我們控制好,在真正運算時,一次全部串接好,再發給資料庫。

SELECT * FROM "coffees_coffee"
WHERE ("coffees_coffee"."name" LIKE '%咖啡%' ESCAPE '\' AND "coffees_coffee"."price" = 300);
Coffee.objects.filter(name__contains='咖啡', price=300)
<QuerySet [<Coffee: 咖啡一號>]>

SELECT * FROM "coffees_coffee"
WHERE ("coffees_coffee"."name" LIKE '%咖啡%' ESCAPE '\' AND "coffees_coffee"."price" = 300);
Coffee.objects.filter(name__contains='咖啡', price=300)
<QuerySet [<Coffee: 咖啡一號>]>

SELECT * FROM "coffees_coffee"
WHERE ("coffees_coffee"."name" LIKE '%咖啡%' ESCAPE '\' AND NOT ("coffees_coffee"."price" = 300));
Coffee.objects.filter(name__contains='咖啡').exclude(price=300)
<QuerySet [<Coffee: 咖啡二號>, <Coffee: 咖啡三號>]>

更新多筆 (UPDATE MANY)

這裡會把符合條件的資料都進行欄位更新,並回傳更新筆數。

UPDATE "coffees_coffee" SET "roast" = 'High' WHERE "coffees_coffee"."price" > 300;
Coffee.objects.filter(price__gt=300).update(roast='High')
2

新增多對多資料 (ADD MANY TO MANT Field)

多對多無法直接塞在 create 的參數裡面,要透過下面這樣用欄位 add 的方式塞進去。

INSERT INTO "coffees_tag" ("created_at", "updated_at", "name")
VALUES ('2021-05-01 07:56:32.746569', '2021-05-01 07:56:32.746610', '經典');
>>> tag_classic = Tag.objects.create(name='經典')

INSERT INTO "coffees_tag" ("created_at", "updated_at", "name")
VALUES ('2021-05-01 07:56:32.754009', '2021-05-01 07:56:32.754050', '水洗法');
>>> tag_wash = Tag.objects.create(name='水洗法')

INSERT OR IGNORE INTO "coffees_coffee_tags" ("coffee_id", "tag_id")
SELECT 1, 1 UNION ALL SELECT 1, 2;
>>> coffee1.tags.add(tag_classic, tag_wash)

SELECT * FROM "coffees_tag"
INNER JOIN "coffees_coffee_tags"
ON ("coffees_tag"."id" = "coffees_coffee_tags"."tag_id")
WHERE "coffees_coffee_tags"."coffee_id" = 1;
>>> coffee1.tags.all()
<QuerySet [<Tag: 經典>, <Tag: 水洗法>]>

刪除 (DELETE)

刪除時會把關聯的資料也一併清除,刪除基本上是不可逆的,請三思後再進行。 第一種是較為安全的單筆,第二種多筆刪除前,請確定好過濾正確。

DELETE FROM "coffees_coffee_tags" WHERE "coffees_coffee_tags"."coffee_id" IN (1);
DELETE FROM "coffees_coffee" WHERE "coffees_coffee"."id" IN (1);
>>> coffee1.delete()
(3, {'coffees.Coffee_tags': 2, 'coffees.Coffee': 1})

SELECT * FROM "coffees_coffee";
>>> Coffee.objects.all()
<QuerySet [<Coffee: 咖啡二號>, <Coffee: 咖啡三號>]>

SELECT * FROM "coffees_coffee";
DELETE FROM "coffees_coffee_tags" WHERE "coffees_coffee_tags"."coffee_id" IN (2, 3);
DELETE FROM "coffees_coffee" WHERE "coffees_coffee"."id" IN (3, 2);
>>> Coffee.objects.all().delete()
(2, {'coffees.Coffee': 2})

SELECT * FROM "coffees_coffee";
>>> Coffee.objects.all()
<QuerySet []>

results matching ""

    No results matching ""