内容简介:postgres 是 django 官方推荐使用的数据库。为什么使用 postgres 以及 mysql 和 postgres 各有什么优劣不是这篇文章的重点,如果感兴趣可以参考下面这些文章:django 的 model有一些只针对于 postgres 的 fields,这篇文章就简要地介绍一下这些 pg specific fields,然后还会追踪到 pg 相对于的 feature(因为这一切都是以 pg 强大的特性作为支持的)。本文的例子全部来源于django的官方文档。
postgres 是 django 官方推荐使用的数据库。为什么使用 postgres 以及 mysql 和 postgres 各有什么优劣不是这篇文章的重点,如果感兴趣可以参考下面这些文章:
- What are pros and cons of PostgreSQL and MySQL? With respect to reliability, speed, scalability, and features
- PostgreSQL vs MySQL
- PostgreSQL Vs. MySQL: Differences In Performance, Syntax, And Features
- Why I Choose PostgreSQL Over MySQL/MariaDB
- PostgreSQL 与 MySQL 相比,优势何在?
django 的 model有一些只针对于 postgres 的 fields,这篇文章就简要地介绍一下这些 pg specific fields,然后还会追踪到 pg 相对于的 feature(因为这一切都是以 pg 强大的特性作为支持的)。
本文的例子全部来源于django的官方文档。
Field 类型一览:
- ArrayField
- JSONField
- HStoreField
- Range Field
ArrayField
定义
class ArrayField(base_field, size=None, **options) 复制代码
base_field
参数
有一个必选的参数 base_field
,挺好理解,一个 Array 得指定元素类型。所以你可以传入 IntegerField
、 CharField
、 TextField
,但是不能传 ForeignKey
, OneToOneField
, ManyToManyField
。 ArrayField还能实现嵌套列表的功能! 请看下面这个例子:
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)[]
类型的字段:
可以这样插入数据:
c = ChessBoard() c.board = [["a", "b", "c"], ["d", "e", "f"]] c.save() 复制代码
这里有一点是需要注意的,那就是传入的嵌套列表长度要是一样的,不然会触发异常:
django.db.utils.DataError: multidimensional arrays must have array expressions with matching dimensions 复制代码
这是因为 pg 本身对于 multidimensional array
类型数据做了这个限制:
上图来源于pg官方文档:8.15. Arrays
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 *
一下:
query查询
以这个 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>]> 复制代码
JSONField
定义
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', }] } }) 复制代码
Key, index, and path lookups
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
一样有下面几个查询方法:
- contains
- contained_by
- has_key
- has_any_keys
- has_keys
HStoreField
定义
class HStoreField(**options) 复制代码
用来存储键值对类型数据,对应 Python 数据类型为 dict,但是 key 必须是字符串,value 必须是字符串或者 null。
如果要使用这个 field,还需要做两步额外的事情:
- 将
django.contrib.postgres
添加到INSTALLED_APPS
。 - 开启 PG 的 hstore extension
第二步是要修改一个 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>]> 复制代码
values
>>> 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>]> 复制代码
Range Fields
pg 还支持范围类型的数据。比如 IntegerRangeField
, BigIntegerRangeField
, DecimalRangeField
等,篇幅有限,这里就不讲了。有兴趣的情看官方文档。
总结一下
pg 很强大,非常强大,不只是一个关系型数据库,能实现的功能很多很多。尤其是内置的数据类型极其丰富。
pg 还有自带的全文搜索功能,你甚至不需要额外使用 elasticsearch;pg 针对 json 类型的数据做了索引优化,能实现 mongo 等非关系型数据库的功能。这也难怪 django 官方首推的数据库是 pg 了。
如果你像我一样真正热爱计算机科学,喜欢研究底层逻辑,欢迎关注我的微信公众号:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Jakarta Struts Cookbook中文版
斯格科 / 清华大学 / 2007-7 / 56.00元
Jakarta Struts Cookbook(中文版),ISBN:9787302155638,作者:(美)斯格科一起来看看 《Jakarta Struts Cookbook中文版》 这本书的介绍吧!