Edited at

DjangoのQuerySet.delete が発行するクエリ

More than 3 years have passed since last update.

Djangoの QuerySet.delete() を実行した時に実際に、発行されるクエリがちょっと想定と違ったので、メモ。Django1.7、データベースはMySQLで確認しています。


準備


  • 以下のようなmodels.pyを用意。


books/models.py

from django.db import models

class Author(models.Model):
name = models.CharField(max_length=100)

class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)



  • テストデータを用意。

>>> from books.models import *

>>> a1 = Author.objects.create(name='Alice')
>>> a2 = Author.objects.create(name='Bob')
>>> Book.objects.create(title='book1', author=a1)
>>> Book.objects.create(title='book2', author=a1)
>>> Book.objects.create(title='comic1', author=a2)
>>> Book.objects.create(title='comic2', author=a2)


実験

QuerySet.delete() をいろいろな条件で実行してみた。


  • 単にfieldで絞り込む場合

>>> Book.objects.filter(title='comic1').delete()

DELETE FROM `books_book` WHERE `books_book`.`title` = 'comic1';

素直にDELETE FROM テーブル WHERE 条件 だった。


  • 外部テーブルを参照する場合

>>> Book.objects.filter(author__name='Bob').delete()

SELECT `books_book`.`id` FROM `books_book` INNER JOIN `books_author` ON ( `books_book`.`author_id` = `books_author`.`id` ) WHERE `books_author`.`name` = 'Bob';
DELETE FROM `books_book` WHERE `books_book`.`id` IN (11, 10);

SELECT id FROM テーブル INNER JOIN ... WHERE 条件 してから

DELETE FROM テーブル WHERE id IN (...) していた。


  • 親テーブルをdeleteする場合

>>> Author.objects.all().delete()

SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author`;
DELETE FROM `books_book` WHERE `books_book`.`author_id` IN (7, 8);
DELETE FROM `books_author` WHERE `books_author`.`id` IN (8, 7);

SELECT id FROM 親テーブル INNER JOIN ... WHERE 条件してから、子テーブルの関連するレコードをDELETEして、その後親テーブルのレコードをDELETE。

ここ曰く、


Django は、オブジェクトを削除する際に、通常 SQLでいう ON DELETE CASCADE 制約をエミュレートします。すなわち、 削除対象のオブジェクトを指すような外部キーを持つ全てのオブジェクトも 同時に削除されるのです。


とのこと。リンク先の例ではインスタンスのdeleteメソッドだけど、QuerySetでも同じような動作になるようだ。


まとめ

外部テーブル参照したり、親テーブルの場合、想定通りの動作か確認しておいたほうがいい。

deleteに限らず、ORM経由で操作するときはどんなクエリになるか確認しておいたほうが良さげ。