+1

Performing raw SQL queries in Django (Part I)

Như các bạn đã biết, Django cung cấp sẵn một bộ API (QuerySet) cho phép thực hiện các thao tác CRUD.

Các bạn có thể tham khảo ở đây:

Bộ API này đơn giản hóa việc thao tác với data trong Django. Cú pháp đơn giản là điểm mạnh của bộ API này. Nhưng một khi câu query quá phức tạp hoặc khó viết với QuerySet, chúng ta cần sử dụng đến các câu query SQL thuần.

Trong bài viết này, tôi sẽ đề cập đến việc thao tác với database bằng các câu lệnh SQL thuần.

Có hai cách để thực hiện các câu query SQL thuần trong Django. Thứ nhất là sử dụng Manager.raw() để thực hiện các câu query và trả về các instance. Thứ hai, chúng ta sẽ dùng các câu lệnh SQL một cách trực tiếp. Ở cách thứ hai, chúng ta tránh được việc sử dụng lớp model (model layer).

1. Manager.raw(raw_query, params=None, translations=None)

Method manager raw() có thể được sử dụng để thực hiện các câu query SQL thuần mà ở đó, giá trị trả về là các instance.

Method này nhận câu query SQL làm tham số đầu vào. Sau khi xử lý, nó trả về một instance django.db.models.query.RawQuerySet. Instance này có thể được lặp giống như với một QuerySet thông thường.

Tốt nhất là chúng ta nên có một ví dụ để minh họa 😃 Giả sử chúng ta có model sau:


class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)

Chúng ta có thể thực thi các câu lệnh SQL kiểu như:


for p in Person.objects.raw("SELECT * FROM myapp_person")
    print p

...

Khanh Ngo
Nobody Else

Tất nhiên là ví dụ này chẳng có gì hay ho cả - nó giống như sử dụng QuerySet Person.objects.all(). Tuy nhiên, raw() có một số các option khác làm cho nó trở nên mạnh mẽ.

  • Model table names

    Tên của bảng Person đến từ đâu trong ví dụ trên?

    Mặc định, Django tìm ra tên một bảng trong database bằng cách nối "app label" của model - tên mà bạn sử dụng trong manage.py startapp - với tên class của model bằng một dấu gạch dưới "_" ở giữa chúng. Ở trong ví dụ, chúng ta giả dụ rằng Person tồn trong app tên là myapp. Khi đó, bảng trong câu query sẽ là myapp_person

    Để đi sâu hơn, bạn có thể tham khảo tài liệu về db_table.

  • Warning 1

    Các câu lệnh SQL truyền vào .raw() sẽ không được kiểm tra. Django mong muốn các câu lệnh sẽ trả về một tập các row từ database nhưng chẳng làm gì để ép buộc điều đó. Nếu câu query không trả về các row, một lỗi sẽ được trả về. Chú ý là lỗi này có thể gây khó hiểu cho chúng ta.

  • Warning 2

    Nếu bạn đang thực thi các câu lệnh với MySQL thì phải chú ý rằng việc MySQL tự động ép kiểu dữ liệu có thể gây ra các kết quả không như mong đợi. Ví dụ như nếu bạn query ở cột có kiểu dữ liệu là string với một giá trị integer, MySQL sẽ chuyển các giá trị của cột đó qua integer trước khi thực hiện việc so sánh. Nếu bảng đó chứa các giá trị 'abc', 'def' và bạn query với WHERE mycolumn=0 thì cả hai bản ghi đó đều thỏa mãn.

  • Warning 3

    Tuy một instance RawQuerySet có thể được lặp giống như với QuerySet, nó lại không được implement tất cả các method mà bạn có thể sử dụng với QuerySet. Ví dụ, bool()len() không được định nghĩa trong RawQuerySet và do đó, tất cả các instance RawQuerySet được coi như là giá trị True. Lý do mà những method này không được implement trong RawQuerySet là vì nếu implement chúng mà không có cơ chế lưu cache nội tại (internal caching) có thể sẽ gây ra những hạn chế về performance và việc thêm cơ chế lưu cache đó có thể sẽ gây ra sự không tương thích ngược (backward incompatibility).

a. Mapping query fields to model fields

raw() tự động map các field trong câu query với các field trong model.

Thứ tự của các field trong câu query không quan trọng. Ví dụ, hai câu query sau có ý nghĩa như nhau:


Person.objects.raw('SELECT id, first_name, birth_date FROM myapp_person')
...
Person.objects.raw('SELECT last_name, birth_date, first_name FROM myapp_person')
...

Việc matching được thực hiện thông qua tên field. Điều này có nghĩa là bạn có thể sử dụng mệnh đề AS trong SQL để map các field trong câu query với các field trong model. Vậy nên nếu bạn có một bảng khác có dữ liệu Person trong đó, bạn có thể dễ dàng map nó với các instance Person:


Person.objects.raw('''SELECT first AS first_name,
                             last AS last_name,
                             bd AS birth_date,
                             pk AS id,
                      FROM another_table''')

Chỉ cần tên các field match, các instance sẽ được tạo ra một cách đúng đắn.

Một lựa chọn khác để map các field trong model là truyền tham biến translations vào raw(). Đây là một dictionary map các field trong câu query với các field trong model. Ví dụ, câu query trên có thể được viết lại như sau:


name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM another_table, translations=name_map')

b. Index lookups

raw() hỗ trợ indexing, vậy nên nếu bạn cần duy nhất kết quả đầu tiên, bạn có thể dùng:


first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]

Tuy nhiên, indexing và slicing không được thực hiện ở mức database (database level). Nếu bạn có một lượng lớn các object Person trong database, bạn nên sử dụng mệnh đề LIMIT ở câu lệnh SQL:


first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]

c. Deferring model fields

Các field có thể bị lược bỏ:


people = Person.objects.raw('SELECT id, first_name FROM myapp_person')

Các object Person được trả về bởi câu query là các instance model bị trì hoãn (defferred model instance)(xem defer()). Điều này có nghĩa là các field bị lược bỏ ở câu query sẽ chỉ được load khi có yêu cầu (loaded on demand). Ví dụ:


for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
    print(p.first_name,  # Load trong câu query raw()
          p.last_name)  # Load khi có yêu cầu

...

Khanh Ngo
Nobody Else

Nhìn bề ngoài, nghe có vẻ câu query đã load cả first_name lẫn last_name ngay từ đầu. Tuy nhiên thì chỉ có cột first_name được load bởi câu query raw(), còn cột last_name được load on demand khi câu lệnh print được chạy.

Chỉ có một field duy nhất bạn không thể lược bỏ - primary key. Django sử dụng primary key để nhận biết các instance model, vậy nên nó luôn có mặt trong câu query thuần. Exception InvalidQuery sẽ được raise lên nếu như bạn quên primary key.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.