Django 从持有 IntegerField(s) 的 PostgreSQL 特定 ArrayField 中获取最小值和最大值

Django get min and max value from PostgreSQL specific ArrayField holding IntegerField(s)

我的模型中有一个 A​​rrayField,其中包含 IntegerFields。我正在寻找可以从此数组中提取最小和最大整数的最佳方法。

Django 文档没有给出类似的例子,我想知道是否正确的方法是找出如何以及是否可以在此 ArrayField 上使用 aggregation 函数或如果我能以某种方式将其转换为 normal python list 整数并使用内置的最小值、最大值函数?

执行此操作的任何建议都会有所帮助。例子更多。

Python 的 max 和 min 函数似乎可以做到。

模型定义:

from django.contrib.postgres.fields import ArrayField

class TestArrayField(models.Model):
    integers = ArrayField(
        models.IntegerField()
    )

示例数据(两个 TestArrayField 模型实例,每个实例都有一个 IntegerFields 的数组):

此 returns min/max 对于 TestArrayField 的给定实例:

def test_array_field(request):
    # grabbing the first one, you could use .filter() instead
    integers_array = TestArrayField.objects.first().integers
    max_integer = max(integers_array)
    min_integer = min(integers_array)

    # printing output..
    print(integers_array)
    print('max_integer:', max_integer)
    print('min_integer:', min_integer)

这是输出:

[6, 8, 10, 12]
max_integer: 12
min_integer: 6

这是一个从头开始到使用 PostgreSQL 在可重现的 Docker 容器中工作的 Django 的示例,以及 min/max 通过各种方法在没有 [=91] 的情况下=] 找到示例。我只是有类似的想法,所以我创建它只是为了好玩,如果有帮助,请随时使用它。 :)

从 Docker Compose 开始我创建了 2 个容器 - 一个用于 Django,一个用于 PostgreSQL 并为其设置一些默认值,例如密码、连接性等,并安装当前的覆盖文件系统的工作目录,无需重建即可按需编辑。

用于测试的示例 docker-compose.yml

version: "3.7"
services:
    my-postgres:
        image: postgres:alpine
        environment:
            POSTGRES_PASSWORD: password
    django:
        build:
            context: "."
            dockerfile: Dockerfile
        environment:
            ENGINE: "django.db.backends.postgresql_psycopg2"
            USER: postgres
            PASSWORD: password
            HOST: "my-postgres"
            PORT: 5432
            NAME: postgres
        volumes:
            - .:/tmp

对于客户端,有必要有一个编译器(除非你有不同的包),headers,共享 objects(库)。对于 Alpine,您还需要 musl-dev 用于 limits.h 和其他 headers + 共享 objects.

二手 Dockerfile:

FROM python:alpine
RUN apk add postgresql postgresql-dev gcc musl-dev
WORKDIR /tmp
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . ./
WORKDIR /tmp/app
ENTRYPOINT python manage.py runserver

requirements.txt

asgiref==3.3.1
Django==3.1.6
pytz==2021.1
sqlparse==0.4.1
psycopg2==2.8.6

Tree

在当前目录中发出了 django-admin 命令,这就是为什么您会看到 app/app 结构,这看起来可能很奇怪。这是因为 django-admin 命令中项目的命名为 app.

.
├── app
│   ├── app
│   │   ├── asgi.py
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── manage.py
│   └── pg
│       ├── admin.py
│       ├── apps.py
│       ├── __init__.py
│       ├── migrations
│       │   ├── 0001_initial.py
│       │   └── __init__.py
│       ├── models.py
│       ├── tests.py
│       └── views.py
├── docker-compose.yml
├── Dockerfile
└── requirements.txt

如何 运行:

docker-compose build
docker-compose up -d
docker-compose logs -f                            # to check the logs
docker-compose exec django python manage.py shell # to play in REPL
docker-compose exec django sh                     # to play in shell

修改文件:

settings.py:

# modified
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'pg'
]

from os import environ
DATABASES = {
    'default': {
        item: environ.get(item)
        for item in ["ENGINE", "NAME", "USER", "PASSWORD", "HOST", "PORT"]
    }
}
# modified end

pg申请

它是一个单独的 Django 应用程序,通过创建的 manage.py 文件在一个空的 Django 项目中从头开始创建:

django-admin startproject app
cd app
python manage.py startapp pg

# edit models.py before migrations step (of course)
python manage.py makemigrations pg
python manage.py migrate

pg/models.py:

from django.db import models
from django.contrib.postgres.fields import ArrayField

class MyModel(models.Model):
    array = ArrayField(models.IntegerField())

    def __str__(self):
        return f"<My Model ({self.id})>: {self.array}"

Insert

与普通的 Django 模型一样:

>>> from pg import models
>>> models.MyModel.objects.all()
<QuerySet []>
>>> models.MyModel.objects.create(array=[1,5,9])
<MyModel: MyModel object (1)>
>>> models.MyModel.objects.create(array=[2,1,0])
<MyModel: MyModel object (2)>
>>> models.MyModel.objects.all()
<QuerySet [<MyModel: <My Model (1)>: [1, 5, 9]>, <MyModel: <My Model (2)>: [2, 1, 0]>]>

Min/Max

可以通过多种方式完成,具体取决于您是想利用 Python 还是聚合

>>> # single object min/max, uses Django i.e. your machine
>>> min(models.MyModel.objects.get(id=1).array)
1
>>> max(models.MyModel.objects.get(id=1).array)
9
>>> # sum min/max, same machine
>>> min([sum(item.array) for item in models.MyModel.objects.all()])
3
>>> max([sum(item.array) for item in models.MyModel.objects.all()])
15

Aggregating

>>> from django.db.models import Min, Max
>>> models.MyModel.objects.aggregate(Min("array"))
{'array__min': [1, 5, 9]}
>>> models.MyModel.objects.aggregate(Max("array"))
{'array__max': [2, 1, 0]}

通过 Django 本身在数组项上执行似乎相当有问题,但是有一点点 raw query 就没有问题(至少使用 min()):

from django.db.models.expressions import RawSQL
>>> models.MyModel.objects.annotate(max=RawSQL(
    "select (select max(x) from unnest(pg_mymodel.array) x)",
    params=[]
)).values("max")
<QuerySet [{'max': 9}, {'max': 2}]>

即使是单品:

>>> models.MyModel.objects.filter(id=2).annotate(max=RawSQL(
    "select (select max(x) from unnest(pg_mymodel.array) x)",
    params=[]
)).values("max")
<QuerySet [{'max': 2}]>

根据您的用例,可能 cheaper/faster 利用 PostgreSQL 来完成繁重的工作(准备数据),或者只是在不从数据库中进行太多编辑的情况下提取原始数据作为“原始”,然后例如通过多处理将数据处理成你想要的形状利用你的机器进行繁重的计算。

如果您真的不喜欢原始查询,您可以 create your own aggregating Expression。尽管有时只是 easier/faster 使用 pre-made 数据创建 SQL 视图(可以通过迁移创建),为此构建模型并通过 ORM 提取数据。使用最好的工具来完成工作,但如果不是为了性能需要,不要花太多时间在上面。