理解django的多对多ManyToManyField

对于第一次碰到django这样类activerecord的ORM,初学者可能比较疑惑的是ManyToManyField这个字段。老鸟可以绕开,这里拿djangobook没有说明的地方来仔细解释下。
from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

有出版商,作者,和书。一本书有多个作者,只有一个出版商。
作者和出版商好理解,各一个表就是了。书应该作为几个表呢?1个和2个都可以。如果你主要是以出版商和作者为对象操作,可以把书看成一个纽带而已,书这个表里存放着出版商和作者的关系。又因为一行存不下所有作者的id(假设没有压缩),所以book表里面会有很多book会重复。所以book表的名字改成author_publisher搞不好还更妥当。
如果你要认真的把书也看成一个表(不想看到重复的书名),那么就需要把书和作者的关系又单独提出来。这里是个多对多的关系所以用ManyToManyField,如果一对多呢?就用ForeignKey。
我们用
python manage.py sql books
查看生成的表结构
BEGIN;
CREATE TABLE "books_publisher" (
    "id" serial NOT NULL PRIMARY KEY,
    "name" varchar(30) NOT NULL,
    "address" varchar(50) NOT NULL,
    "city" varchar(60) NOT NULL,
    "state_province" varchar(30) NOT NULL,
    "country" varchar(50) NOT NULL,
    "website" varchar(200) NOT NULL
)
;
CREATE TABLE "books_author" (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(40) NOT NULL,
    "email" varchar(75) NOT NULL
)
;
CREATE TABLE "books_book" (
    "id" serial NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id") DEFERRABLE INITIALLY DEFERRED,
    "publication_date" date NOT NULL
)
;
CREATE TABLE "books_book_authors" (
    "id" serial NOT NULL PRIMARY KEY,
    "book_id" integer NOT NULL REFERENCES "books_book" ("id") DEFERRABLE INITIALLY DEFERRED,
    "author_id" integer NOT NULL REFERENCES "books_author" ("id") DEFERRABLE INITIALLY DEFERRED,
    UNIQUE ("book_id", "author_id")
)
;
CREATE INDEX "books_book_publisher_id" ON "books_book" ("publisher_id");
COMMIT;

结果确实是生成了四个表。
其中book_authors表是关联表,不能直接插入数据,实际上也不存在叫做BookAuthors的对象。所以要插入这里面数据,建立起book和author的联系时,必须取出book实例,并给book赋值
#首先是创建一个book,book创建之后才能添加联系表,这是显然的
book = Book()
book.save()
#添加三个作者,传如一个列表
book.authors = Author.objects.all()[0:3]
#或者添加一个作者,传入一个实例
book.authors.add(Author.objects.all()[0])
#最后是save
book.save()

那么,使用ManyToManyField的好处是不是就是省去了创建一个简单联系表(如果不满足于这么简单的表,也可一通过through参数来指明存在的表)?使用它我们还可以做到通过把一张表中某键值在另一张表中全部映射的对象找出来。比如把某书的所有作者,或者某作者的所有书找出来。
book.authors.all()
author.book_set.all()

可是如果用最土的三张表的方法:一个publisher,一个author,一个publisher_author,在PublisherAuthor模型不指定ManyToManyField而只用ForeignKey也可以这么方便么?(找出映射的对象)
猜想是可以的……可以查出publisher或author对应的PublisherAuthor对象……就相当与只执行了
select publisher_author.* from publisher_author, author where publisher_author.author_id = author.id

我们还得拿着这些author的id才能找出真正的这些author。
而之前的ManyToManyField做了什么呢?
select * from publisher where publisher.id in (select publisher_id from publisher_author, author where publisher_author.author_id = author.id) 

嗯……多对多关系只是帮我们多做了一步嵌套子查询,并包装成publisher集而已,更方便,但未必更高效。

注:以上SQL是伪的,未经过查验,根据结果反推,ORM至少是做了这些工作的,只会更多,不会更少。如果有机会我会再查查它到底执行了什么,如果知道结果的朋友也请告诉我吧XD

猜你喜欢

转载自luozhaoyu.iteye.com/blog/1510635