Query Expressions in Django (Part I)
Query expression mô tả một giá trị hoặc một tính toán được sử dụng trong update, create, filter, order by, annotation hay aggregate. Có một số built-in expression có thể giúp bạn trong việc viết các câu query. Các expression có thể được kết hợp hoặc trong một số trường hợp là lồng nhau để thực hiện ...
Query expression mô tả một giá trị hoặc một tính toán được sử dụng trong update, create, filter, order by, annotation hay aggregate. Có một số built-in expression có thể giúp bạn trong việc viết các câu query. Các expression có thể được kết hợp hoặc trong một số trường hợp là lồng nhau để thực hiện các tính toán phức tạp.
Supported arithmetic
Django hỗ trợ cộng, trừ, nhân, chia, số học modulo và lũy thừa ở các biểu thức query, cũng như là sử dụng hằng số, biến và thậm chí cả các expression khác trong Python.
Some examples
from django.db.models import F, Count from django.db.models.functions import Length, Upper, Value # Tìm các công ty có số lượng nhân viên nhiều hơn số lượng ghế ngồi Company.objects.filter(num_employees__gt=F('num_chairs')) # Tìm các công ty có số lượng nhân viên bằng ít nhất hai lần số lượng ghế ngồi # Hai câu lệnh sau là tương đương Company.objects.filter(num_employees__gt=F('num_chairs') * 2) Company.objects.filter(num_employees__gt=F('num_chairs') + F('num_chairs')) # Số lượng ghế cần mua thêm để tất cả nhân viên của mỗi công ty đều có ghế ngồi company = Company.objects.filter( num_employees__gt=F('num_chairs')).annotate( chairs_needed=F('num_employees') - F('num_chairs')).first() company.num_employees => 120 company.num_chairs => 50 company.chairs_needed => 70 # Thêm mới một công ty company = Company.objects.create(name='Google', ticker=Upper(Value('goog'))) # Refresh lại object nếu muốn truy cập trường dữ liệu company.refresh_from_db() company.ticker => 'GOOG' # Annotate model với một giá trị được tính toán # Hai lệnh sau tương đương Company.objects.annotate(num_products=Count('products')) Company.objects.annotate(num_products=Count(F('products'))) # Aggregate cũng có thể gồm các tính toán phức tạp Company.objects.annotate(num_offerings=Count(F('products') + F('services'))) # Các expression cũng có thể được sử dụng trong mệnh đề order_by Company.objects.order_by(Length('name').asc()) Company.objects.order_by(Length('name').desc())
Built-in Expressions
-
Note
Những expression này được định nghĩa trong django.db.models.expressions và django.db.models.aggregates. Nhưng ở đây, để tiện lợi, chúng ta import chúng từ django.db.models.
F() expressions
class F[source]
Một object F() biểu diễn giá trị của một model field hoặc một cột được annotate (annotated column). Điều này giúp cho chúng ta có thể refer các giá trị model field và thực hiện các thao tác cơ sở dữ liệu (database operation) mà không cần kéo chúng ra khỏi database và lưu vào bộ nhớ Python (Python memory).
Thay vào đó, Django sử dụng object F() để sinh ra một SQL expression mô tả thao tác được yêu câu ở mức database (database level).
Cách tốt nhất để hiểu là thông qua ví dụ. Thông thường, một ai đó có thể viết ra một số thứ như kiểu:
# Tintin đệ trình một mẩu truyện mới reporter = Reporter.objects.get(name="Tintin") reporter.stories_filed += 1 reporter.save()
Ở đây, chúng ta kéo giá trị của reporter.stories_filed từ database vào trong memory và thao tác với nó sử dụng các toán tử Python quen thuộc. Sau đó save object vào database. Nhưng thay vì như vậy, chúng ta có thể làm như sau:
from django.db.models import F reporter = Reporters.objects.get(name="Tintin") reporter.stories_filed = F("stories_filed") + 1 reporter.save()
Mặc dù reporter.stories_filed = F("stories_filed") + 1 trông giống như một phép gán Python thông thường, thực tế nó lại là một lệnh SQL (SQL instruct) mô tả thao tác với database.
Khi Django phát hiện một object F(), nó sẽ override các toán tử Python chuẩn để tạo ra một SQL expression. Trong trường hợp này chính là expression chỉ dẫn cho database tăng giá trị của trường dữ liệu (database field) được biểu diễn bởi reporter.stories_filed.
Chú ý là ở đây, Python không bao giờ biết được giá trị của reporter.stories_filed, dù là giá trị mới hay giá trị cũ. Việc cập nhật giá trị này được xử lý hoàn toàn bởi database. Tất cả những gì Python làm, thông qua object F() của Django, là tạo ra cú pháp SQL (SQL syntax) để refer tới trường dữ liệu và mô tả thao tác.
Để truy cập giá trị mới được lưu theo cách này, object phải được load lại:
reporter = Reporter.objects.get(pk=reporter.pk) # Hoặc ngắn gọn hơn: reporter.refresh_from_db()
Ngoài việc được sử dụng trong thao tác với các instance riêng lẻ, F() cũng có thể được sử dụng với QuerySet gồm nhiều instance với update(). Ở đây, hai câu query ở trên - get() và save() sẽ bị lược bỏ và được thay thế bởi:
reporter = Reporters.objects.filter(name="Tintin") reporter.update(stories_filed=F("stories_filed") + 1)
Chúng ta cũng có thể sử dụng update() để tăng giá trị trường dữ liệu cho nhiều object - việc này nhanh hơn rất nhiều so với việc kéo tất cả chúng từ database vào Python, lặp từng object, tăng từng giá trị trường rồi save từng bản ghi một vào database:
Reporter.objects.all().update(stories_filed=F("stories_filed") + 1)
Do đó, F() giúp ta tối ưu performance bằng cách:
- Sử dụng database để làm việc, thay vì Python
- Giảm lượng query mà các thao tác yêu cầu
Avoiding race conditions using F()
Một lợi ích khác của F() chính là việc sử dụng database để update giá trị của một trường dữ liệu sẽ tránh được tranh đoạt điều khiển (race condition).
Nếu có hai thread Python cùng xử lý code trong ví dụ đầu tiên ở bên trên, một thread có thể lấy ra được, tăng và save một trường dữ liệu sau khi thread còn lại đã lấy ra được nó từ trong database. Giá trị mà thread thứ hai save dựa vào giá trị ban đầu. Khi đó, công việc mà thread thứ nhất làm sẽ hoàn toàn bị mất hết.
Nếu database chịu trách nhiệm update trường dữ liệu, tiến trình sẽ thông minh hơn: nó sẽ chỉ update trường dựa vào các giá trị hiện tại trong database khi thực hiện save() và update().
F() assignments persist after Model.save()
Các object F() được gán cho các model field sẽ được giữ nguyên sau khi thực hiện save một model instance và sẽ được thực hiện ở mỗi lần thực hiện save(). Ví dụ:
reporter = Reporter.objects.get(name="Tintin") reporter.stories_filed = F("stories_filed") + 1 reporter.save() reporter.name = "Tintin Jr." reporter.save()
stories_filed sẽ được update hai lần trong trường hợp này. Nếu giá trị ban đầu là 1 thì giá trị cuối cùng sẽ là 3.
Using F() in filters
F() rất hữu ích trong mệnh đề filter. Ở đó, chúng giúp thực hiện việc filter đối với một tập các object thông qua các tiêu chuẩn dựa vào giá trị trường của chính nó thay vì các giá trị Python.
Tìm hiểu cách sử dụng biểu thức F() trong query ở đây.
Using F() with annotations
F() có thể được sử dụng để tạo ra các trường động (dynamic field) cho model của bạn bằng cách kết hợp các trường với tính toán số học:
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
Nếu các trường được kết hợp có kiểu dữ liệu khác nhau, bạn phải khai báo cho Django biết kiểu dữ liệu của trường được trả về. Bởi vì F() không trực tiếp support output_field nên bạn phải wrap expression với ExpressionWrapper:
from django.db.models import DateTimeField, ExpressionWrapper, F Ticket.objects.annotate( expires=ExpressionWrapper( F("active_at") + F("duration"), output_field=DateTimeField()))
Khi refer các trường quan hệ (relational field) như ForeignKey, F() trả về giá trị primary key thay vì một model instance:
>> car = Company.objects.annotate(built_by=F("manufacturer"))[0] >> car.manufacturer <Manufacturer: Toyota> >> car.built_by 3
Phần I của loạt bài viết về Query Expressions in Django xin được dừng lại ở đây