【问题标题】:Filter list items within a Django JSONField过滤 Django JSONField 中的列表项
【发布时间】:2019-10-08 02:08:10
【问题描述】:

这是我正在研究的 Django 模型的简化版本。

class Course(models.Model):
    infos = JSONField()

信息 JSONField 如下所示:

infos = {
    category: "Maths",
    students: [
        {
            name: "Alice",
            result: 8
        },
        {
            name: "Bob",
            result: 12
        }
    ]
}

# (students can have 0 -> n items)

我正在尝试获取在任何课程中至少获得 10 分的所有学生的列表(结果 >= 10)。 但是,我无法找到相应地过滤 QuerySet 的方法。

我正在尝试做这样的事情:

(Course.objects.filter(students__result__gte=10)
               .values_list('students', flat=True))

但由于学生是一个列表,我无法直接访问每个项目的结果属性。此外,我认为它不会从结果中排除“Alice”对象。

我想得到这样的结果:

items = [
    {
        name: "Bob",
        result: 12
    }
]

如果有办法将过滤的学生和课程类别联系起来,则可以加分:

items = [
    {
        category: "Maths",
        students: [
            {
                name: "Bob",
                result: 12
            }
        ]
    }
 ]

我怎样才能达到预期的效果?

【问题讨论】:

  • 你不知道学生人数,是吗?
  • 我事先不知道那个数字,这意味着我不能通过索引来定位数组项。我没有为此找到任何“小丑”,例如:students__???__result__gte(其中 ??? 将在每个学生的循环中被替换)
  • 我认为你不能使用 Django 的 ORM 来实现这一点。因为,当您使用“跨度”时(students__result__gte=8)。如果此条件返回 True,则将返回整行。我认为您应该在 python 方面对此进行排序。
  • 你是对的,这是有道理的,因为这是过滤课程行而不是行内的学生列表。谢谢!
  • 我建议使用 JSONField 如果您的数据不适合使用常规关系建模的数据库。此外,您的数据结构似乎非常适合关系模型

标签: python django postgresql django-jsonfield


【解决方案1】:

通过使用原始 SQL,我设法获得了结果。

select array_to_json(array_agg(students_array))
FROM
    course_table,
    json_array_elements(CAST(course_table.infos->>'students' as json)) students_array
WHERE
    CAST(students_array->>'result' as integer) >= 10
;

结果

sample_db=# SELECT version();
PostgreSQL 10.12 (Ubuntu 10.12-0ubuntu0.18.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0, 64-bit

sample_db=# select infos from course_table;
{"category": "Maths", "students": [{"name": "Alice", "result": 8}, {"name": "Bob", "result": 12}]}
{"category": "Science", "students": [{"name": "Jerin", "result": 8}, {"name": "George", "result": 12}]}
{"category": "Physics", "students": [{"name": "Vivek", "result": 17}, {"name": "Osama", "result": 6}]}

sample_db=# select array_to_json(array_agg(students_array))
sample_db-# FROM
sample_db-# course_table,
sample_db-#  json_array_elements(CAST(course_table.infos->>'students' as json)) students_array
sample_db-# WHERE
sample_db-#  CAST(students_array->>'result' as integer) >= 10
sample_db-# ;
[{"name": "Bob", "result": 12},{"name": "George", "result": 12},{"name": "Vivek", "result": 17}]

这个原始 SQL 可以被 Django 执行为,

raw_sql = """
select 1 as id, array_to_json(array_agg(students_array)) as result
FROM
    course_table,
    json_array_elements(CAST(course_table.infos->>'students' as json)) students_array
WHERE
    CAST(students_array->>'result' as integer) >= 10
;
"""
qs = Course.objects.raw(raw_sql)
for i in qs:
    print(i.result)

【讨论】:

  • 我很高兴找到基于 Django 的方式
  • 我知道,但是,如果你没有基于 ORM 的查询,你可以选择这个。
  • 我所有的查询都是基于 Django-ORM 的
  • 我希望您已经知道如何使用 .raw(raw_sql) 方法执行原始 SQL。 (如果没有,请查看更新的答案)
  • 我正在寻找一种纯粹的 Django-ORM 方式...没有任何 SQL
【解决方案2】:

如果您使用的是 PostgreSQL 数据库,则可以切换到 HStoreField([docs here][1])。 HStoreField 将允许您准确使用您提到的查找 (students__result__gte)。否则,最具 Django 风格的方法是创建一个学生模型,如 Arakkal 在您的问题下的 cmets 中建议的那样。

编辑:这是最简单的过滤方式,因为所有地图/字典都相同(如果您真的想要与 JSON 相同的结构):

class Course(models.Model):
    pass

class Info(models.Model):
    course = models.ForeignKey(Course, related_name='infos')
    category = models.CharField(max_length=128)
    # students = models.HStoreField()  # available in postgresql

class Student(models.Model):
    """If not using PostgreSQL, also more Django-esque."""
   info = models.ForeignKey(Info, related_name='students')

   name = models.CharField(max_length=128)
   result = models.IntegerField()

我正在尝试获取在任何课程中至少获得 10 分的所有学生的列表(结果 >= 10)

鉴于您的目标和我对逻辑关系的直觉,这将是我的首选结构:

class Course(models.Model):
    students = models.ManyToManyField(Student, through=Result)
    category = models.CharField(max_length=128)

class Student(models.Model):
    name = models.CharField(max_length=128)

class Result(models.Model):
    course = models.ForeignKey(Course, on_delete=models.deletion.CASCADE)
    student = models.ForeignKey(Student, on_delete=models.deletion.CASCADE)
    grade = models.IntegerField()

这允许:Result.objects.filter(grade__gte=10).values_list('student__name', flat=True)

【讨论】:

  • 嗨@Jura,我的数据不是直接的键值,我的数据结构是键列表
  • 不确定你的意思,如果它只是一个列表,那么 PostgreSQL 在 ArrayField 上具有相同的功能。 (您发布的 JSON 是否包含键值对,不是吗?)。如果问题很深,我强烈建议在模型结构中解决这个问题。您能否详细说明您想使用 jsons 而不是模型的原因是什么?
  • 我有一个地图数组(我做错了,写了一个键列表),我的问题是我对这个数组的长度视而不见,我没有任何神奇的查询后缀__gt 等表示数组的每一项。
  • 您确定每张地图的结构,还是它们不同? (可以肯定 map 和 dict 实际上与数据结构相同)
  • 那么你肯定会是使用完整的 Django 实现的最快乐的人。如果您想尝试一下,我在上面添加了一个实现。
【解决方案3】:

您可以尝试为您的课程班级定义custom manager。这将使您能够以 Django-ORM 样式进行您想要的查询,而无需使用原始 SQL。

PS:- 如果适合您的需要,我将使用更多代码和详细信息编辑此答案。

【讨论】:

  • 嗨@Nikhil-Khandelwal,我想通过聚合与 F、Func、Cast 相同的东西来处理这个问题,我不想在任何地方使用任何 SQL。
猜你喜欢
  • 2016-03-25
  • 2016-07-23
  • 2018-10-02
  • 2020-10-16
  • 2021-09-23
  • 2020-10-08
  • 2020-03-22
  • 1970-01-01
  • 2018-08-22
相关资源
最近更新 更多