postgres 是 django 官方推荐使用的数据库。为何使用 postgres 以及 mysql 和 postgres 各有什么优劣不是这篇文章的重点,若是感兴趣能够参考下面这些文章:javascript
django 的 model有一些只针对于 postgres 的 fields,这篇文章就简要地介绍一下这些 pg specific fields,而后还会追踪到 pg 相对于的 feature(由于这一切都是以 pg 强大的特性做为支持的)。html
本文的例子所有来源于django的官方文档。java
Field 类型一览:python
class ArrayField(base_field, size=None, **options)
复制代码
base_field
参数有一个必选的参数base_field
,挺好理解,一个 Array 得指定元素类型。因此你能够传入IntegerField
、CharField
、TextField
,可是不能传ForeignKey
, OneToOneField
, ManyToManyField
。 ArrayField还能实现嵌套列表的功能! 请看下面这个例子:mysql
from django.contrib.postgres.fields import ArrayField
from django.db import models
class ChessBoard(models.Model):
board = ArrayField(
ArrayField(
models.CharField(max_length=10, blank=True),
size=8,
),
size=8,
)
复制代码
这个会在数据库中生成一个character varying(10)[]
类型的字段:sql
能够这样插入数据:数据库
c = ChessBoard()
c.board = [["a", "b", "c"], ["d", "e", "f"]]
c.save()
复制代码
这里有一点是须要注意的,那就是传入的嵌套列表长度要是同样的,否则会触发异常:express
django.db.utils.DataError: multidimensional arrays must have array expressions with matching dimensions
复制代码
这是由于 pg 自己对于multidimensional array
类型数据作了这个限制:django
上图来源于pg官方文档:8.15. Arraysjson
size
参数可选,指定 Array的最大长度。可是事实上pg 并不会作强制限制,若是你插入的列表长度超过了size
,不会报错,仍是能成功执行:
c = ChessBoard()
c.board = [
["a", "b", "c"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
["d", "e", "f"],
]
c.save()
复制代码
select *
一下:
以这个 model 为例:
from django.contrib.postgres.fields import ArrayField
from django.db import models
class Post(models.Model):
name = models.CharField(max_length=200)
tags = ArrayField(models.CharField(max_length=200), blank=True)
def __str__(self):
return self.name
复制代码
生成的 table 为:
contains
插入三条数据:
Post.objects.create(name='First post', tags=['thoughts', 'django'])
Post.objects.create(name='Second post', tags=['thoughts'])
Post.objects.create(name='Third post', tags=['tutorial', 'django'])
复制代码
过滤tags包含某个 tag 的数据:
tags__contains
传入的是一个列表
>>> Post.objects.filter(tags__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contains=['django'])
<QuerySet [<Post: First post>, <Post: Third post>]>
>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
<QuerySet [<Post: First post>]>
复制代码
contained_by
和contains
相反,这个查询的是 tags 是传入数据的 subset。
>>> Post.objects.filter(tags__contained_by=['thoughts', 'django'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contained_by=['thoughts', 'django', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
复制代码
overlap
只要包含其中一个就好了,也就是你传入的列表范围越大,查询到的数据可能性就越多。而前面的contains
传入的列表越长,获得的数据可能就越少。
>>> Post.objects.filter(tags__overlap=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__overlap=['thoughts', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
复制代码
len
根据ArrayField
的长度进行查询。
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.filter(tags__len=1)
<QuerySet [<Post: Second post>]>
复制代码
Index transforms
查询列表的某个特定元素(pg是否是有点强大~),任何非负数均可以,若是超过了 size 也不会报错。
>>> Post.objects.filter(tags__0='thoughts')
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__1__iexact='Django')
<QuerySet [<Post: First post>]>
>>> Post.objects.filter(tags__276='javascript')
<QuerySet []>
复制代码
Slice transforms
和Index transforms
相似,可是不是针对某个元素,而是一个 slice:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['django', 'python', 'thoughts'])
>>> Post.objects.filter(tags__0_1=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__0_2__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
复制代码
class JSONField(encoder=None, **options)
复制代码
Python的这些native format均可以用:dictionaries, lists, strings, numbers, booleans, None.
下文的示例用的是这个 model:
class Dog(models.Model):
name = models.CharField(max_length=200)
data = JSONField()
def __str__(self):
return self.name
复制代码
data
字段是一个jsonb
类型数据:
插入数据示例:
Dog.objects.create(name='Rufus', data={
'breed': 'labrador',
'owner': {
'name': 'Bob',
'other_pets': [{
'name': 'Fishy',
}],
}
})
复制代码
encoder
参数可选,何时有用呢?当你的数据不是 Python native 类型的时候,好比 uuid、datetime等。 这个时候能够用DjangoJSONEncoder
或任何知足需求的json.JSONEncoder
子类。
上面那个 model 插入非 python native 对象的时候就会报错,好比插入datetime.datetime.now()
会提示:
TypeError: Object of type datetime is not JSON serializable
复制代码
若是你非要插入datetime 类型的数据,可使用DjangoJSONEncoder(详细的官方文档在这里),也就是:
from django.core.serializers.json import DjangoJSONEncoder
class Dog(models.Model):
name = models.CharField(max_length=200)
data = JSONField(encoder=DjangoJSONEncoder)
复制代码
而后执行下面这条插入语句不会报错了:
Dog.objects.create(name='Rufus', data={
'breed': 'labrador',
'owner': {
'name': 'Bob',
'other_pets': [{
'name': 'Fishy',
}],
},
'birthday': datetime.datetime.now()
})
复制代码
须要注意的是,pg 真正存储的时候,仍是用字符串存的,并且取出来的时候,不会自动转回 datetime,仍是字符串。
dog = Dog.objects.filter(name='Rufus')[1]
print(type(dog.data['birthday']))
复制代码
<class 'str'>
复制代码
我想,django 不为你自动转换的缘由,应该是考虑到存进去是字符串,有可能只是刚好那个字符串长得像 datetime 格式,强行转换可能并非你想要的结果。而你要比 django 更清楚数据是什么类型的,何时须要转换何时不须要。
插入测试数据:
Dog.objects.create(name='Rufus', data={
'breed': 'labrador',
'owner': {
'name': 'Bob',
'other_pets': [{
'name': 'Fishy',
}]
}
})
复制代码
Dog.objects.filter(data__owner=None)
Dog.objects.filter(data__breed='collie')
Dog.objects.filter(data__owner__name='Bob')
Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
# 查询 missing 的 key,使用 isnull
>>> Dog.objects.create(name='Shep', data={'breed': 'collie'})
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>
复制代码
是否是和 mongo 对 json 类型数据的支持同样强大!
和下面要讲的HStoreField
同样有下面几个查询方法:
class HStoreField(**options)
复制代码
用来存储键值对类型数据,对应 Python 数据类型为 dict,可是 key 必须是字符串,value 必须是字符串或者 null。
若是要使用这个 field,还须要作两步额外的事情:
django.contrib.postgres
添加到INSTALLED_APPS
。第二步是要修改一个migrations
文件:
好比这样的一个model:
class Dog(models.Model):
name = models.CharField(max_length=200)
data = HStoreField()
def __str__(self):
return self.name
复制代码
原始的 migrations
文件是这样的:
import django.contrib.postgres.fields.hstore
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('goods', '0004_auto_20190514_1502'),
]
operations = [
migrations.CreateModel(
name='Dog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('data', django.contrib.postgres.fields.hstore.HStoreField()),
],
),
]
复制代码
咱们须要作一点点修改:在operations的最前面添加一个HStoreExtension()
。
from django.contrib.postgres.operations import HStoreExtension
class Migration(migrations.Migration):
...
operations = [
HStoreExtension(),
...
]
复制代码
最终的 migrations 文件是这样的:
import django.contrib.postgres.fields.hstore
from django.db import migrations, models
from django.contrib.postgres.operations import HStoreExtension
class Migration(migrations.Migration):
dependencies = [
('goods', '0004_auto_20190514_1502'),
]
operations = [
HStoreExtension(),
migrations.CreateModel(
name='Dog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('data', django.contrib.postgres.fields.hstore.HStoreField()),
],
),
]
复制代码
关于在 migrations 里面添加数据库插件的功能情看官方文档。
这个第二步是不能少的。 否则会报如下错误:
can't adapt type 'dict' if you skip the first step, or type "hstore" does not exist 复制代码
Key lookups
根据某个 key 的值查询:
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})
>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>
复制代码
还能够链式调用其余的查询方法:
>>> Dog.objects.filter(data__breed__contains='l')
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
复制代码
contains
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})
>>> Dog.objects.filter(data__contains={'owner': 'Bob'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>
复制代码
contained_by
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})
>>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>
复制代码
has_key
根据是否包含某个 key 做为查询条件。
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__has_key='owner')
<QuerySet [<Dog: Meg>]>
复制代码
has_any_keys
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})
>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
复制代码
has_keys
>>> Dog.objects.create(name='Rufus', data={})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
<QuerySet [<Dog: Meg>]>
复制代码
keys
>>> Dog.objects.create(name='Rufus', data={'toy': 'bone'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__keys__overlap=['breed', 'toy'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
复制代码
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__values__contains=['collie'])
<QuerySet [<Dog: Meg>]>
复制代码
pg 还支持范围类型的数据。好比IntegerRangeField
,BigIntegerRangeField
,DecimalRangeField
等,篇幅有限,这里就不讲了。有兴趣的情看官方文档。
pg 很强大,很是强大,不仅是一个关系型数据库,能实现的功能不少不少。尤为是内置的数据类型极其丰富。
pg 还有自带的全文搜索功能,你甚至不须要额外使用 elasticsearch;pg 针对 json 类型的数据作了索引优化,能实现 mongo 等非关系型数据库的功能。这也难怪 django 官方首推的数据库是 pg 了。
若是你像我同样真正热爱计算机科学,喜欢研究底层逻辑,欢迎关注个人微信公众号: