【问题标题】:Django get min and max value from PostgreSQL specific ArrayField holding IntegerField(s)Django 从 PostgreSQL 特定的 ArrayField 中获取最小值和最大值,其中包含 IntegerField(s)
【发布时间】:2021-05-19 16:17:09
【问题描述】:

我的模型中有一个 ArrayField,其中包含 IntegerFields。我正在寻找从这个数组中提取最小和最大整数的最佳方法。

Django 文档没有给出类似的例子,我想知道正确的方法是找出如何以及是否可以在这个 ArrayField 上使用 aggregation 函数,或者我是否可以强制转换以某种方式普通的 Python 列表 并使用内置的 min、max 函数?

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

【问题讨论】:

    标签: python django django-orm


    【解决方案1】:

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

    模型定义:

    from django.contrib.postgres.fields import ArrayField
    
    class TestArrayField(models.Model):
        integers = ArrayField(
            models.IntegerField()
        )
    

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

    这将返回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
    

    【讨论】:

    • 太棒了,谢谢!似乎 integers_array 类型的行为类似于 python list 或者它实际上是一个?奇怪的是为什么文档没有提到这一点。
    • 没错,其实如果你在上面的例子中运行type(integers_array),它会显示它实际上是一个列表。
    【解决方案2】:

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

    从 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
    

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

    使用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}]>
    

    根据您的用例,利用 PostgreSQL 进行繁重的工作(准备数据)或只是从数据库中提取原始数据而无需大量编辑为“原始”,然后例如通过多处理将数据处理成您想要的形状,利用您的机器进行繁重的计算。

    如果你真的不喜欢原始查询,你可以create your own aggregating Expression。尽管有时使用预制数据创建 SQL 视图(可以通过迁移创建)、为此构建模型并通过 ORM 提取数据更容易/更快。使用最适合这项工作的工具,但如果不需要性能,请不要在上面花费太多时间。

    【讨论】:

      猜你喜欢
      • 2020-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多