Django 内部连接与跨多对多关系的表的搜索词
Django inner join with search terms for tables across many-to-many relationships
我创建了一个动态分层高级搜索界面。基本上,它允许您从大约 6 或 7 个 table 中搜索词条,这些词条全部链接在一起,可以以任何组合形式与或运算。表格中的搜索条目全部编译成复杂的Q表达式。
今天发现一个问题。如果我为多对多相关子 table 中的字段提供搜索词,输出 table 可能包含来自 table 的与该词不匹配的结果。
我的问题可以通过简单的查询在 shell 中重现:
qs = PeakGroup.objects.filter(msrun__sample__animal__studies__id__exact=3)
sqs = qs[0].msrun.sample.animal.studies.all()
sqs.count()
#result: 2
具体来说:
In [3]: qs = PeakGroup.objects.filter(msrun__sample__animal__studies__id__exact=3)
In [12]: ss = s[0].msrun.sample.animal.studies.all()
In [13]: ss[0].__dict__
Out[13]:
{'_state': <django.db.models.base.ModelState at 0x7fc12bfbf940>,
'id': 3,
'name': 'Small OBOB',
'description': ''}
In [14]: ss[1].__dict__
Out[14]:
{'_state': <django.db.models.base.ModelState at 0x7fc12bea81f0>,
'id': 1,
'name': 'obob_fasted',
'description': ''}
sqs
查询集中的 id
包括 1 和 3,即使我只搜索了 3。我不会按字面意思返回 所有 研究, 所以它正在过滤一些不匹配的研究记录。我明白为什么会看到这个,但我不知道如何执行一个查询,将其视为我可以在 SQL 中执行的连接,我可以将结果限制为仅包含与查询匹配的记录,而不是仅返回根模型中的记录并收集左连接到这些根模型记录的所有内容。
有没有办法对整组链接的 table 执行这样的内部联接(作为过滤器中单个复杂 Q 表达式的结果),以便我只取回匹配的记录M:M 字段搜索词?
更新:
通过查看 SQL:
In [3]: str(s.query)
Out[3]: 'SELECT "DataRepo_peakgroup"."id", "DataRepo_peakgroup"."name", "DataRepo_peakgroup"."formula", "DataRepo_peakgroup"."msrun_id", "DataRepo_peakgroup"."peak_group_set_id" FROM "DataRepo_peakgroup" INNER JOIN "DataRepo_msrun" ON ("DataRepo_peakgroup"."msrun_id" = "DataRepo_msrun"."id") INNER JOIN "DataRepo_sample" ON ("DataRepo_msrun"."sample_id" = "DataRepo_sample"."id") INNER JOIN "DataRepo_animal" ON ("DataRepo_sample"."animal_id" = "DataRepo_animal"."id") INNER JOIN "DataRepo_animal_studies" ON ("DataRepo_animal"."id" = "DataRepo_animal_studies"."animal_id") WHERE "DataRepo_animal_studies"."study_id" = 3 ORDER BY "DataRepo_peakgroup"."name" ASC'
...我可以看到查询与我希望的一样具体,但是在模板中,我如何指定我想要在 SQL 结果中看到的内容,我是否提供了我想在输出中看到的所有特定相关 table 字段?例如:
SELECT "DataRepo_peakgroup"."id", "DataRepo_peakgroup"."name", "DataRepo_peakgroup"."formula", "DataRepo_peakgroup"."msrun_id", "DataRepo_peakgroup"."peak_group_set_id"
, "DataRepo_animal_studies"."study_id"
FROM "DataRepo_peakgroup" INNER JOIN "DataRepo_msrun" ON ("DataRepo_peakgroup"."msrun_id" = "DataRepo_msrun"."id") INNER JOIN "DataRepo_sample" ON ("DataRepo_msrun"."sample_id" = "DataRepo_sample"."id") INNER JOIN "DataRepo_animal" ON ("DataRepo_sample"."animal_id" = "DataRepo_animal"."id") INNER JOIN "DataRepo_animal_studies" ON ("DataRepo_animal"."id" = "DataRepo_animal_studies"."animal_id") WHERE "DataRepo_animal_studies"."study_id" = 3 ORDER BY "DataRepo_peakgroup"."name" ASC
所有 Django ORM 从查询(其过滤器使用来自 many-to-many [henceforth "M:M"] 相关 tables 的字段)返回的是一组记录来自查询开始的“root”table。它使用您将在 SQL 查询中使用的连接逻辑,使用外键获取记录,因此您可以保证取回 DO link 到 [=27] 的根 table 记录=]a M:M 相关 table 与您的搜索词匹配的记录,但是当您发送要在模板中呈现的根 table 记录(/queryset)并插入一个从 M:M 相关的 table 访问记录的嵌套循环,它总是得到 一切 linked 到那个根 table 记录 -无论它们是否匹配您的搜索词,因此如果您将 link 的根 table 记录返回到 M:M 相关 table 中的多条记录,至少有 1 条记录保证与您的搜索词相匹配,但其他记录可能不会。
为了获得 inner 连接(即仅包括与查询的 table(s) 中的搜索词匹配的组合记录),您只需 roll-your-own,因为Django不支持。我通过以下方式完成此操作:
在呈现搜索结果时,无论您要在何处包含来自 M:M 相关 table 的记录,都必须创建嵌套循环。在 inner-most 循环中,我基本上 re-enforce 记录匹配所有搜索词。 IE。我对每个记录组合(从根 table 和每个 M:M 相关的 table)使用条件实现我自己的过滤器。如果任何记录组合不匹配,我将跳过它。
关于我如何做到这一点的细节,我不会太过纠结,但在每个嵌套循环中,我都将其关键路径(例如 msrun__sample__animal__studies
)的字典作为关键和当前记录作为值。然后,我使用自定义 simple_tag
到 re-run 搜索词针对每个 table 中的当前记录(通过检查搜索词的关键路径与字典中可用的那些)。
或者,您可以在视图中通过编译所有匹配记录并将组合的大 table 发送到模板来执行此操作 - 但我选择不这样做,因为 [=57] 周围存在障碍=] 并且因为我已经将搜索词表单数据传递给 re-display 执行的搜索表单,所以搜索的结构已经可用。
注意#1 - 即使没有“重新过滤”模板中的组合记录,请注意记录计数 可能 不准确在处理 combined/composite/joined table 时。为了确保我在 table 上方的 header 中报告的记录数始终是 correct/accurate(即代表我的 html table 中的行数) ,我跟踪过滤后的计数并使用 table 下的 javascript 来更新顶部报告的记录计数。
注意#2 - 在使用 M:M 相关 table 中的术语进行查询时,还需要牢记另一件事。对于与搜索词匹配的每个 M:M 相关 table 记录,您将从根 table 取回重复记录,并且无法区分它们之间的区别(即哪个 M:M record/field 值在每种情况下都匹配。例如,如果您匹配来自 M:M 相关 table 的 2 条记录,您将得到 2 条相同的根 table 记录,并且当您将该数据传递给模板时,您最终会在结果中得到 4 行,每行都包含根 table 记录中的相同数据。要避免这种情况,您所要做的就是附加 .distinct()
过滤你的结果查询集。
我创建了一个动态分层高级搜索界面。基本上,它允许您从大约 6 或 7 个 table 中搜索词条,这些词条全部链接在一起,可以以任何组合形式与或运算。表格中的搜索条目全部编译成复杂的Q表达式。
今天发现一个问题。如果我为多对多相关子 table 中的字段提供搜索词,输出 table 可能包含来自 table 的与该词不匹配的结果。
我的问题可以通过简单的查询在 shell 中重现:
qs = PeakGroup.objects.filter(msrun__sample__animal__studies__id__exact=3)
sqs = qs[0].msrun.sample.animal.studies.all()
sqs.count()
#result: 2
具体来说:
In [3]: qs = PeakGroup.objects.filter(msrun__sample__animal__studies__id__exact=3)
In [12]: ss = s[0].msrun.sample.animal.studies.all()
In [13]: ss[0].__dict__
Out[13]:
{'_state': <django.db.models.base.ModelState at 0x7fc12bfbf940>,
'id': 3,
'name': 'Small OBOB',
'description': ''}
In [14]: ss[1].__dict__
Out[14]:
{'_state': <django.db.models.base.ModelState at 0x7fc12bea81f0>,
'id': 1,
'name': 'obob_fasted',
'description': ''}
sqs
查询集中的 id
包括 1 和 3,即使我只搜索了 3。我不会按字面意思返回 所有 研究, 所以它正在过滤一些不匹配的研究记录。我明白为什么会看到这个,但我不知道如何执行一个查询,将其视为我可以在 SQL 中执行的连接,我可以将结果限制为仅包含与查询匹配的记录,而不是仅返回根模型中的记录并收集左连接到这些根模型记录的所有内容。
有没有办法对整组链接的 table 执行这样的内部联接(作为过滤器中单个复杂 Q 表达式的结果),以便我只取回匹配的记录M:M 字段搜索词?
更新:
通过查看 SQL:
In [3]: str(s.query)
Out[3]: 'SELECT "DataRepo_peakgroup"."id", "DataRepo_peakgroup"."name", "DataRepo_peakgroup"."formula", "DataRepo_peakgroup"."msrun_id", "DataRepo_peakgroup"."peak_group_set_id" FROM "DataRepo_peakgroup" INNER JOIN "DataRepo_msrun" ON ("DataRepo_peakgroup"."msrun_id" = "DataRepo_msrun"."id") INNER JOIN "DataRepo_sample" ON ("DataRepo_msrun"."sample_id" = "DataRepo_sample"."id") INNER JOIN "DataRepo_animal" ON ("DataRepo_sample"."animal_id" = "DataRepo_animal"."id") INNER JOIN "DataRepo_animal_studies" ON ("DataRepo_animal"."id" = "DataRepo_animal_studies"."animal_id") WHERE "DataRepo_animal_studies"."study_id" = 3 ORDER BY "DataRepo_peakgroup"."name" ASC'
...我可以看到查询与我希望的一样具体,但是在模板中,我如何指定我想要在 SQL 结果中看到的内容,我是否提供了我想在输出中看到的所有特定相关 table 字段?例如:
SELECT "DataRepo_peakgroup"."id", "DataRepo_peakgroup"."name", "DataRepo_peakgroup"."formula", "DataRepo_peakgroup"."msrun_id", "DataRepo_peakgroup"."peak_group_set_id"
, "DataRepo_animal_studies"."study_id"
FROM "DataRepo_peakgroup" INNER JOIN "DataRepo_msrun" ON ("DataRepo_peakgroup"."msrun_id" = "DataRepo_msrun"."id") INNER JOIN "DataRepo_sample" ON ("DataRepo_msrun"."sample_id" = "DataRepo_sample"."id") INNER JOIN "DataRepo_animal" ON ("DataRepo_sample"."animal_id" = "DataRepo_animal"."id") INNER JOIN "DataRepo_animal_studies" ON ("DataRepo_animal"."id" = "DataRepo_animal_studies"."animal_id") WHERE "DataRepo_animal_studies"."study_id" = 3 ORDER BY "DataRepo_peakgroup"."name" ASC
所有 Django ORM 从查询(其过滤器使用来自 many-to-many [henceforth "M:M"] 相关 tables 的字段)返回的是一组记录来自查询开始的“root”table。它使用您将在 SQL 查询中使用的连接逻辑,使用外键获取记录,因此您可以保证取回 DO link 到 [=27] 的根 table 记录=]a M:M 相关 table 与您的搜索词匹配的记录,但是当您发送要在模板中呈现的根 table 记录(/queryset)并插入一个从 M:M 相关的 table 访问记录的嵌套循环,它总是得到 一切 linked 到那个根 table 记录 -无论它们是否匹配您的搜索词,因此如果您将 link 的根 table 记录返回到 M:M 相关 table 中的多条记录,至少有 1 条记录保证与您的搜索词相匹配,但其他记录可能不会。
为了获得 inner 连接(即仅包括与查询的 table(s) 中的搜索词匹配的组合记录),您只需 roll-your-own,因为Django不支持。我通过以下方式完成此操作:
在呈现搜索结果时,无论您要在何处包含来自 M:M 相关 table 的记录,都必须创建嵌套循环。在 inner-most 循环中,我基本上 re-enforce 记录匹配所有搜索词。 IE。我对每个记录组合(从根 table 和每个 M:M 相关的 table)使用条件实现我自己的过滤器。如果任何记录组合不匹配,我将跳过它。
关于我如何做到这一点的细节,我不会太过纠结,但在每个嵌套循环中,我都将其关键路径(例如 msrun__sample__animal__studies
)的字典作为关键和当前记录作为值。然后,我使用自定义 simple_tag
到 re-run 搜索词针对每个 table 中的当前记录(通过检查搜索词的关键路径与字典中可用的那些)。
或者,您可以在视图中通过编译所有匹配记录并将组合的大 table 发送到模板来执行此操作 - 但我选择不这样做,因为 [=57] 周围存在障碍=] 并且因为我已经将搜索词表单数据传递给 re-display 执行的搜索表单,所以搜索的结构已经可用。
注意#1 - 即使没有“重新过滤”模板中的组合记录,请注意记录计数 可能 不准确在处理 combined/composite/joined table 时。为了确保我在 table 上方的 header 中报告的记录数始终是 correct/accurate(即代表我的 html table 中的行数) ,我跟踪过滤后的计数并使用 table 下的 javascript 来更新顶部报告的记录计数。
注意#2 - 在使用 M:M 相关 table 中的术语进行查询时,还需要牢记另一件事。对于与搜索词匹配的每个 M:M 相关 table 记录,您将从根 table 取回重复记录,并且无法区分它们之间的区别(即哪个 M:M record/field 值在每种情况下都匹配。例如,如果您匹配来自 M:M 相关 table 的 2 条记录,您将得到 2 条相同的根 table 记录,并且当您将该数据传递给模板时,您最终会在结果中得到 4 行,每行都包含根 table 记录中的相同数据。要避免这种情况,您所要做的就是附加 .distinct()
过滤你的结果查询集。