django数据库查询总结
博客应用数据库:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self): # __unicode__ on Python 2
return self.headline
创建对象(INSERT)
save()
对应的INSERT 数据,没有返回对象
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
create()
创建并保存一个对象
Blog.objects.create(name='Beatles Blog', tagline='All the latest Beatles news.')
等价下面
p = Blog.objects.create(name='Beatles Blog', tagline='All the latest Beatles news.')
p.save(force_insert=True)
更新数据(update)
假设Blog 的一个实例b5 已经被保存在数据库中,下面这个例子将更改它的name 并且更新数据库中的记录:
>>> b5.name = 'New name'
>>> b5.save()
ForeignKey外键更新
from blog.models import Entry
entry = Entry.objects.get(pk=1)
cheese_blog = Blog.objects.get(name="Cheddar Talk")
entry.blog = cheese_blog
entry.save()
ManyToManyField字段,多数据级联更新
- 需要使用字段的add()方法来增加关联关系的一条记录。
from blog.models import Author
joe = Author.objects.create(name="Joe")
entry.authors.add(joe)
- 看看更新多条记录:
john = Author.objects.create(name="John")
paul = Author.objects.create(name="Paul")
george = Author.objects.create(name="George")
ringo = Author.objects.create(name="Ringo")
entry.authors.add(john, paul, george, ringo)
获取对象 select
你可以从模型的管理器那里取得查询集。每个模型都至少有一个管理器,它默认命名为objects。通过模型类来直接访问它,你也可以自定义一个管理器,如:
from django.db import models
class Person(models.Model):
#...
people = models.Manager()
使用例子中的模型, Person.objects会抛出AttributeError异常,而Person.people.all()会返回一个包含所有Person对象的列表。
- 用默认管理器获取对象
Blog.objects
b = Blog(name='Foo', tagline='Bar')
b.objects # 这样会报错
管理器只可以通过模型的类访问,而不可以通过模型的实例访问,目的是为了强制区分“表级别”的操作和“记录级别”的操作
获取所有对象
all_entries = Entry.objects.all()
通过get 获取一个单一的对象
one_entry = Entry.objects.get(pk=1
还可以使用all()、 get()、filter() 和exclude()。等方法查询
使用过滤器获取特定对象
- all() 方法返回了一个包含数据库表中所有记录查询集。但在通常情况下,你往往想要获取的是完整数据集的一个子集。
filter(**kwargs)
返回一个新的查询集,它包含满足查询参数的对象。exclude(**kwargs)
返回一个新的查询集,它包含不满足查询参数的对象。
例子
- 要获取年份为2006的所有文章的查询集:
Entry.objects.filter(pub_date__year=2006)
利用默认的管理器,它相当于:
Entry.objects.all().filter(pub_date__year=2006)
- 链式过滤 对应于WHERE 查询集的筛选结果本身还是查询集,所以可以将筛选语句链接在一起。像下面的例子,最开始获取数据库中所有对象的一个查询集,之后增加一个过滤器,然后又增加一个排除,再之后又是另外一个过滤器。最后的结果仍然是一个查询集,它包含标题以”What“开头、发布日期在2005年1月30日至当天之间的所有记录:
Entry.objects.filter(headline__startswith='What').exclude(pub_date__gte=datetime.date.today()).filter(pub_date__gte=datetime(2005, 1, 30))
过滤后的查询是独立
这三个查询集都是独立的。第一个是一个基础的查询集,包含所有标题以“What”开头的记录。第二个查询集是第一个的子集,它增加另外一个限制条件,排除pub_date 为今天和将来的记录。第三个查询集同样是第一个的子集,它增加另外一个限制条件,只选择pub_date 为今天或将来的记录。原始的查询集(q1)不会受到筛选过程的影响。
q1 = Entry.objects.filter(headline__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.date.today())
q3 = q1.filter(pub_date__gte=datetime.date.today())
限制查询
- 返回前面5 个对象(LIMIT 5):
Entry.objects.all()[:5]
- 下面这条语句返回第6 至第10 个对象(OFFSET 5 LIMIT 5):
Entry.objects.all()[5:10]
- 下面的语句返回数据库中根据标题排序后的第一条Entry
Entry.objects.order_by('headline')[0]
字段查询 对应于where
上面说的它们通过查询集方法filter()、exclude() 和 get() 的关键字参数指定。
exact
精确查找
Entry.objects.get(headline__exact="Man bites dog")
Blog.objects.get(id__exact=14)
Blog.objects.get(id=14) 这个和上面的相等
-
iexact
大小写不敏感
Blog.objects.get(name__iexact="beatles blog")
contains
大小写敏感的包含关系测试
Entry.objects.get(headline__contains='Lennon')
当然也可以使用icontains
,大小写不敏感
startswith
,endswith
以某某开头,某某结尾,当然还有大小写不敏感的版本,叫做istartswith
和iendswith
跨关联关系的查询
Django 提供一种强大而又直观的方式来“处理”查询中的关联关系,它在后台自动帮你处理JOIN。 若要跨越关联关系,只需使用关联的模型字段的名称,并使用双下划线分隔,直至你想要的字段:
下面这个例子获取所有Blog 的name 为'Beatles Blog' 的Entry 对象:
Entry.objects.filter(blog__name='Beatles Blog')
- 下面的示例获取所有的Blog 对象,它们至少有一个Entry 的headline 包含'Lennon':
Blog.objects.filter(entry__headline__contains='Lennon')
- 返回的Blog 对象包括author name 为空的Blog对象,以及authorname不为空但author__name关联的entry __author 为空的对象。如果你不需要后者,你可以这样写:
Blog.objects.filter(entry__authors__isnull=False,
entry__authors__name__isnull=True)
Filter 可以引用模型的字段
F()
将模型的一个字段与同一个模型的另外一个字段进行比较- 为了查找comments 数目多于pingbacks 的Entry,我们将构造一个F() 对象来引用pingback 数目,并在查询中使用该F() 对象
from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks)
- Django 支持对F() 对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作,两个操作数可以都是常数和其它F() 对象。为了查找comments 数目比pingbacks 两倍还要多的Entry,我们将查询修改为:
Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
- 为了查询rating 比pingback 和comment 数目总和要小的Entry,我们将这样查询:
Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
- 你还可以在F() 对象中使用双下划线标记来跨越关联关,如要获取author 的名字与blog 名字相同的Entry,我们可以这样查询
Entry.objects.filter(authors__name=F('blog__name'))
- 对于date 和date/time 字段,你可以给它们加上或减去一个timedelta 对象。下面的例子将返回发布超过3天后被修改的所有Entry
from datetime import timedelta Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
- 查询的快捷方式pk
Blog.objects.get(id__exact=14) # Explicit form Blog.objects.get(id=14) # __exact is implied Blog.objects.get(pk=14) # pk implies id__exact
- pk 的使用不仅限于__exact 查询 —— 任何查询类型都可以与pk 结合来完成一个模型上对主键的查询
Blog.objects.filter(pk__in=[1,4,7])
Blog.objects.filter(pk__gt=14)
- pk查询在join 中也可以工作。例如,下面三个语句是等同的:
Entry.objects.filter(blog__id__exact=3)
Entry.objects.filter(blog__id=3)
Entry.objects.filter(blog__pk=3)
使用Q 对象进行复杂的查询
- Q 对象 (django.db.models.Q) 对象用于封装一组关键字参数,Q 对象可以使用& 和| 操作符组合起来
- 下面的Q 对象封装一个LIKE 查询:
from django.db.models import Q
Q(question__startswith='What')
- 下面的语句产生一个Q 对象,表示两个"question__startswith" 查询的“OR” :
Q(question__startswith='Who') | Q(question__startswith='What')
它等同于下面的SQL WHERE 子句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
- 可以组合& 和| 操作符查询
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
- 如果一个查询函数有多个Q 对象参数,这些参数的逻辑关系为“AND"
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
等同于:
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
- 查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如
Poll.objects.get(Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),question__startswith='Who')
下面的例子就是错误的:
Poll.objects.get(question__startswith='Who',Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
删除对象
- 删除方法,为了方便,就取名为delete()。这个方法将立即删除对象且没有返回值。例如:
e.delete()
- 下面的语句批量删除pub_date 为2005 的所有Entry 对象
Entry.objects.filter(pub_date__year=2005).delete()
- 有外键的删除
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
- 注意,delete() 是唯一没有在管理器 上暴露出来的查询集方法。这是一个安全机制来防止你意外地请求Entry.objects.delete(),而删除所有 的条目。如果你确实想删除所有的对象,你必须明确地请求一个完全的查询集:
Entry.objects.all().delete()
拷贝模型实例
- 虽然没有内建的方法用于拷贝模型实例,但还是很容易创建一个新的实例并让它的所有字段都拷贝过来。最简单的方法是,只需要将pk 设置为None。利用我们的Blog 示例:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
- 如果你用继承,那么会复杂一些。考虑下面Blog 的子类:
class ThemeBlog(Blog):
theme = models.CharField(max_length=200)
django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3
由于继承的工作方式,你必须设置pk 和 id 都为None:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
这个过程不会拷贝关联的对象。如果你想拷贝关联关系,你必须编写一些更多的代码。在我们的例子中,Entry 有一个到Author 的多对多字段:
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors = old_authors # saves new many2many relations
一次更新多个对象
- 有时你想为一个查询集中所有对象的某个字段都设置一个特定的值。这时你可以使用update() 方法。例如:
# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
- 你只可以对非关联字段和ForeignKey 字段使用这个方法。若要更新一个非关联字段,只需提供一个新的常数值。若要更新ForeignKey 字段,需设置新的值为你想指向的新的模型实例。例如:
>>> b = Blog.objects.get(pk=1)
# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)
- 对update 的调用也可以使用F 表达式 来根据模型中的一个字段更新另外一个字段
Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
然而,与filter 和exclude 子句中的F() 对象不同,在update 中你不可以使用F() 对象引入join —— 你只可以引用正在更新的模型的字段。如果你尝试使用F() 对象引入一个join,将引发一个FieldError:
# THIS WILL RAISE A FieldError
Entry.objects.update(headline=F('blog__name'))
关联的对象
- 当你在一个模型中定义一个关联关系时(例如,ForeignKey、 OneToOneField 或ManyToManyField),该模型的实例将带有一个方便的API 来访问关联的对象。 利用本页顶部的模型,一个Entry 对象e 可以通过blog 属性e.blog 获取关联的Blog 对象。Blog 对象b 可以通过entry_set 属性 b.entry_set.all()访问与它关联的所有Entry 对象。
一对多关系
前向查询
- 如果一个模型具有ForeignKey,那么该模型的实例将可以通过属性访问关联的(外部)对象
e = Entry.objects.get(id=2)
e.blog # Returns the related Blog object.
- 如果ForeignKey 字段有null=True 设置(即它允许NULL 值),你可以分配None 来删除对应的关联性
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
- 注意select_related() 查询集方法递归地预填充所有的一对多关系到缓存中。例如
>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog) # Doesn't hit the database; uses cached version.
>>> print(e.blog) # Doesn't hit the database; uses cached version.
反向查询
- 如果模型有一个ForeignKey,那么该ForeignKey 所指的模型实例可以通过一个管理器返回前一个模型的所有实例。默认情况下,这个管理器的名字为foo_set,其中foo 是源模型的小写名称:
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.
# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()
你可以在ForeignKey 定义时设置related_name 参数来覆盖foo_set 的名称。例如,如果Entry 模型改成blog = ForeignKey(Blog, related_name='entries'),那么上面的示例代码应该改成这样:
>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.
# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()
多对多关系¶
多对多关系的两端都会自动获得访问另一端的API。这些API 的工作方式与上面提到的“方向”一对多关系一样。
唯一的区别在于属性的名称:定义 ManyToManyField 的模型使用该字段的属性名称,而“反向”模型使用源模型的小写名称加上'_set' (和一对多关系一样)。
一个例子可以让它更好理解:
e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')
a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.
类似ForeignKey,ManyToManyField 可以指定related_name。在上面的例子中,如果Entry 中的ManyToManyField 指定related_name='entries',那么Author 实例将使用 entries 属性而不是entry_set。
一对一关系¶
一对一关系与多对一关系非常相似。如果你在模型中定义一个OneToOneField,该模型的实例将可以通过该模型的一个简单属性访问关联的模型。
例如:
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
在“反向”查询中有所不同。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合:
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
如果没有对象赋值给这个关联关系,Django 将引发一个DoesNotExist 异常。
实例可以赋值给反向的关联关系,方法和正向的关联关系一样:
e.entrydetail = ed
通过关联的对象进行查询¶
在关联对象字段上的查询与正常字段的查询遵循同样的规则。当你指定查询需要匹配的一个值时,你可以使用一个对象实例或者对象的主键的值。
例如,如果你有一个id=5 的Blog 对象b,下面的三个查询将是完全一样的:
Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly