【问题标题】:Query Django JSONFields that are a list of dictionaries查询作为字典列表的 Django JSONFields
【发布时间】:2019-11-21 00:20:42
【问题描述】:

给定一个结构为字典列表的 Django JSONField:

# JSONField "materials" on MyModel:
[
    {"some_id": 123, "someprop": "foo"},
    {"some_id": 456, "someprop": "bar"},
    {"some_id": 789, "someprop": "baz"},
]

并给出要查找的值列表:

myids = [123, 789]

我想在这些字典列表中的任何位置查询具有匹配 some_id 的所有 MyModel 实例。我可以这样做一次在字典中搜索一个:

# Search inside the third dictionary in each list:
MyModel.objects.filter(materials__2__some_id__in=myids)

但我似乎无法构造一个查询来一次在所有字典中搜索。这可能吗?

【问题讨论】:

  • “2”从何而来?
  • 2 表示“在每个 MyModel 实例的 JSONField 列表中的第三个字典中搜索。问题是如何消除 2 并在每个列表的所有字典中搜索。

标签: django django-jsonfield


【解决方案1】:

contains 可能会对您有所帮助。应该是这样的:

q_keys = Q()

for _id in myids:
    q_keys |= Q(materials__contains={'some_id': _id})

MyModel.objects.filter(q_keys)

【讨论】:

  • 嗯。我非常熟悉在 JSONField 查询中使用 contains 表达式,但这实际上并不是这里有趣的地方。有趣的是,您正在通过迭代目标匹配来逐步构建 Q 查询。如果与单个查询中的数千个可能匹配项进行比较,不知道这将如何扩展,但我会做一些实验并报告。
  • 感谢您为我指明正确的方向。我发布了另一个答案,其中包含我编写的自定义函数,使用这种“增量 Q 构建”技术。
【解决方案2】:

鉴于来自 Davit Tovmasyan 的线索,通过增加 match_targets 并建立一组 Q 查询来做到这一点,我编写了这个函数,它需要一个要搜索的字段名称、一个要搜索的属性名称和一个目标匹配列表。它返回一个新列表,其中包含匹配的字典和它们来自的源对象。

from iris.apps.claims.models import Claim
from django.db.models import Q


def json_list_search(
    json_field_name: str,
    property_name: str, 
    match_targets: list
    ) -> list:
    """

    Args:
        json_field_name: Name of the JSONField to search in
        property_name: Name of the dictionary key to search against
        match_targets: List of possible values that should constitute a match

    Returns:
        List of dictionaries: [
            {"claim_id": 123, "json_obj": {"foo": "y"},
            {"claim_id": 456, "json_obj": {"foo": "z"}
        ]

    Example:
        results = json_list_search(
            json_field_name="materials_data", 
            property_name="material_id", 
            match_targets=[1, 22]
        )

        # (results truncated):
        [
            {
                "claim_id": 1,
                "json_obj": {
                    "category": "category_kmimsg",
                    "material_id": 1,
                },
            },
            {
                "claim_id": 2,
                "json_obj": {
                    "category": "category_kmimsg",
                    "material_id": 23,
                }
            },
        ]
    """

    q_keys = Q()
    for match_target in match_targets:
        kwargs = {
            f"{json_field_name}__contains": [{property_name: match_target}]
        }
        q_keys |= Q(**kwargs)

    claims = Claim.objects.filter(q_keys)

    # Now we know which ORM objects contain references to any of the match_targets 
    # in any of their dictionaries. Extract *relevant* objects and return them
    # with references to the source claim.
    results = []
    for claim in claims:
        data = getattr(claim, json_field_name)
        for datum in data:
            if datum.get(property_name) and datum.get(property_name) in match_targets:
                results.append({"claim_id": claim.id, "json_obj": datum})

    return results

【讨论】:

  • 看起来不错,将其保存以备将来使用
猜你喜欢
  • 2021-12-18
  • 2020-04-01
  • 2015-11-08
  • 2016-08-14
  • 2020-07-16
  • 2020-11-30
  • 2018-01-14
  • 1970-01-01
  • 2015-11-28
相关资源
最近更新 更多