在 Django 中合并两个具有相同主键的不相关表/模型

2023-11-26

我有两个具有相同主键的不相关表。

ip            mac
11.11.11.11   48-C0-09-1F-9B-54
33.33.33.33   4E-10-A3-BC-B8-9D
44.44.44.44   CD-00-60-08-56-2A
55.55.55.55   23-CE-D3-B1-39-A6

ip            type     owner
22.22.22.22   laptop   John Doe
33.33.33.33   server   XYZ Department
44.44.44.44   VM       Mary Smith
66.66.66.66   printer  ZWV Department

第一个表每分钟自动刷新一次。我无法更改数据库结构或填充它的脚本。

两个表都有ip作为主键。

在视图中,我想显示这样的表格:

ip           mac               type    owner          Alert
11.11.11.11  48-C0-09-1F-9B-54                        Unauthorized
55.55.55.55  23-CE-D3-B1-39-A6                        Unauthorized
22.22.22.22                    laptop  John Doe       Down
66.66.66.66                    printer ZWV Department Down
33.33.33.33  4E-10-A3-BC-B8-9D server  XYZ Department OK
44.44.44.44  CD-00-60-08-56-2A VM      Mary Smith     OK

我该如何建模?我应该将两个主键之一作为另一个主键的外键吗?

一旦代码运行,就会产生大量数据,所以我想确保它足够快。

检索数据最快的方法是什么?


Update:

我尝试使用OneToOneField对于第二个表。

这有助于我获取两个表中的记录以及未经授权的设备的记录(第二个表中缺少 IP):

ip           mac               type    owner          Alert
11.11.11.11  48-C0-09-1F-9B-54                        Unauthorized
55.55.55.55  23-CE-D3-B1-39-A6                        Unauthorized
33.33.33.33  4E-10-A3-BC-B8-9D server  XYZ Department OK
44.44.44.44  CD-00-60-08-56-2A VM      Mary Smith     OK

但我无法获取已关闭的设备(第一个表中缺少 IP):

22.22.22.22                    laptop  John Doe       Down
66.66.66.66                    printer ZWV Department Down

我寻求帮助here,但似乎无法完成OneToOneField


大概的概念

您可以使用qs.union:

  • 创建 2 个模型,它们之间没有任何关系。不要忘记使用class Meta: managed = False
  • 从第一个模型中选择,用子查询进行注释并与第二个模型联合:
from django.db import models
from django.db.models import F, OuterRef, Subquery, Value
from django.db.models.functions import Coalesce

# OperationalDevice fields: ip, mac
# AllowedDevice fields: ip, type, owner

USE_EMPTY_STR_AS_DEFAULT = True

null_char_field = models.CharField(null=True)
if USE_EMPTY_STR_AS_DEFAULT:
    default_value = ''
else:
    default_value = None

# By default Expressions treat strings as "field_name" so if you want to use
# empty string as a second argument for Coalesce, then you should wrap it in
# `Value()`.
# `None` can be used there without wrapping in `Value()`, but in
# `.annotate(type=NoneValue)` it still should be wrapped, so it's easier to
# just "always wrap".
default_value = Value(default_value, output_field=null_char_field)

operational_devices_subquery = OperationalDevice.objects.filter(ip=OuterRef('ip'))


qs1 = (
    AllowedDevice.objects
    .all()
    .annotate(
        mac=Coalesce(
            Subquery(operational_devices_subquery.values('mac')[:1]),
            default_value,
            output_field=null_char_field,
        ),
    )
)

qs2 = (
    OperationalDevice.objects
    .exclude(
        ip__in=qs1.values('ip'),
    )
    .annotate(
        type=default_value,
        owner=default_value,
    )
)

final_qs = qs1.union(qs2)

多个领域的通用方法

可以使用更复杂但“通用”的方法Model._meta.get_fields()。对于“第二个”模型有超过 1 个额外字段的情况(不仅是ip,mac)。示例代码(未测试,但给出一般印象):

# One more import:
from django.db.models.fields import NOT_PROVIDED

common_field_name = 'ip'

# OperationalDevice fields: ip, mac, some_more_fields ...
# AllowedDevice fields: ip, type, owner

operational_device_fields = OperationalDevice._meta.get_fields()
operational_device_fields_names = {_f.name for _f in operational_device_fields}  # or set((_f.name for ...))

allowed_device_fields = AllowedDevice._meta.get_fields()
allowed_device_fields_names = {_f.name for _f in allowed_device_fields}  # or set((_f.name for ...))

operational_devices_subquery = OperationalDevice.objects.filter(ip=OuterRef(common_field_name))

left_joined_qs = (  # "Kind-of". Assuming AllowedDevice to be "left" and OperationalDevice to be "right"
    AllowedDevice.objects
    .all()
    .annotate(
        **{
            _f.name: Coalesce(
                Subquery(operational_devices_subquery.values(_f.name)[1]),
                Value(_f.get_default()),  # Use defaults from model definition
                output_field=_f,
            )
            for _f in operational_device_fields
            if _f.name not in allowed_device_fields_names
            # NOTE: if fields other than `ip` "overlap", then you might consider
            # changing logic here. Current implementation keeps fields from the
            # AllowedDevice
        }
        # Unpacked dict is partially equivalent to this:
        # mac=Coalesce(
        #     Subquery(operational_devices_subquery.values('mac')[:1]),
        #     default_for_mac_eg_fallback_text_value,
        #     output_field=null_char_field,
        # ),
        # other_field = Coalesce(...),
        # ...
    )
)

lonely_right_rows_qs = (
    OperationalDevice.objects
    .exclude(
        ip__in=AllowedDevice.objects.all().values(common_field_name),
    )
    .annotate(
        **{
            _f.name: Value(_f.get_default(), output_field=_f),  # Use defaults from model definition
            for _f in allowed_device_fields
            if _f.name not in operational_device_fields_names
            # NOTE: See previous NOTE
        }
    )
)

final_qs = left_joined_qs.union(lonely_right_rows_qs)

使用 OneToOneField 实现“更好”的 SQL

理论上你可以使用device_info = models.OneToOneField(OperationalDevice, db_column='ip', primary_key=True, related_name='status_info'): in AllowedDevice。在这种情况下,您的第一个 QS 可以在不使用的情况下定义Subquery:

from django.db.models import F

# Now 'ip' is not in field names ('device_info' is there), so add it:
allowed_device_fields_names.add(common_field_name)

# NOTE: I think this approach will result in a more compact SQL query without 
# multiple `(SELECT "some_field" FROM device_info_table ... ) as "some-field"`.
# This also might result in better query performance.
honest_join_qs = (
    AllowedDevice.objects
    .all()
    .annotate(
        **{
            _f.name: F(f'device_info__{_f.name}')
            for _f in operational_device_fields
            if _f.name not in allowed_device_fields_names
        }
    )
)

final_qs = honest_join_qs.union(lonely_right_rows_qs)
# or:
# final_qs = honest_join_qs.union(
#     OperationalDevice.objects.filter(status_info__isnull=True).annotate(**missing_fields_annotation)
# )
# I'm not sure which approach is better performance-wise...
# Commented one will use something like:
# `SELECT ... FROM "device_info_table" LEFT OUTER JOIN "status_info_table" ON ("device_info_table"."ip" = "status_info_table"."ip") WHERE "status_info_table"."ip" IS NULL
#
# So it might be a little better than first with `union(QS.exclude(ip__in=honest_join_qs.values('ip'))`.
# Because later uses SQL like this:
# `SELECT ... FROM "device_info_table" WHERE NOT ip IN (SELECT ip FROM "status_info_table")`
#
# But it's better to measure timings of both approaches to be sure.
# @GrannyAching, can you compare them and tell in the comments which one is better ?

附:要自动化模型定义,您可以使用manage.py inspectdb

附言或许多表继承与定制OneToOneField(..., parent_link=True)可能比使用对您更有帮助union.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Django 中合并两个具有相同主键的不相关表/模型 的相关文章

随机推荐

  • Android 权限和权限组之间的链接

    我是 Android 开发新手 我正在查看 android 清单权限 有两件事 权限和权限组 我知道该列表位于 http developer android com reference android Manifest permission
  • 具有并发请求的 Python XMLRPC

    我正在寻找一种方法来防止多个主机向 Python XMLRPC 侦听器同时发出命令 侦听器负责运行脚本以在该系统上执行任务 如果多个用户尝试同时发出这些命令 这些任务将会失败 有没有办法可以阻止所有传入请求 直到单个实例完成 我认为 pyt
  • 在 NSString 对象中查找子字符串

    我有一个 NSString 对象 我想通过定位一个单词来从中创建一个子字符串 例如 我的字符串是 狗吃了猫 我希望程序找到单词 ate 并创建一个子字符串 the cat 有人可以帮助我或给我一个例子吗 Thanks Sagiftw NSR
  • 使用 C# 列出回收站中的文件名,而不使用任何外部文件

    我想要一个使用 c 代码检索回收站 在 win 7 上 中的文件名的函数 该框架似乎没有包含任何内容来实现这一目标 目录 Getfiles 不会起作用 是吗 我发现自己有一个使用 windows shell32自动化 的代码 但这需要提供i
  • Play框架表单只有18个参数

    我观察到 当我向 Play Framework Form class 添加超过 18 个参数时 我会收到一个很长 对我来说难以理解 的编译错误 这是有记录的限制吗 我需要在表单帖子中接收多达 29 个参数 我不决定参数的设计和数量 因为我正
  • 如何将结构体成员设为私有?

    我在头文件中定义了一个结构 如下所示 typedef struct void data point 我想阻止其他人直接访问 data 所以我想我应该在 c 文件中声明结构并使用类似的东西extern typedef struct point
  • 模板中的 Django settings.py 变量

    我遇到了一个非常奇怪的错误 我在我的应用程序中定义了一个应用程序IDsettings py像这样的文件 CARDSPRING APP ID 这几乎适用于我网站上的每个页面 除了一个页面 奇怪的是 其他变量也起作用 在页面的脚本部分中 我有以
  • Android.mk - 构建目录中的所有源文件

    我正在使用 Android NDK 构建我的 cocos2dx 项目 在 Android mk 中 有一个 LOCAL SRC FILES 的定义 其中列出了每个 cpp 文件 每当我添加新的源文件时 我也需要将它添加到那里 它看起来像这样
  • SQL:如何为每天的每个组选择最大值?

    假设我有一个包含以下列的表 Name Date Number 并假设我们将以下数据插入到这些列中 Bob 2011 11 22 1 Bob 2011 11 22 5 Bob 2011 11 22 4 Bob 2011 11 22 3 Wen
  • 自动更新的 iOS 应用程序,用于企业分发

    是否有可能制作一个iOS应用企业分布透明地自我更新 说 更新 我的意思是完全更新其逻辑 这种方法的一个可能用途是远程维护的自助服务亭 iPad 不可能直接更新应用程序二进制文件 但我看到了一些可能性 具有单个 UIWebView 且逻辑在
  • 如何像 Facebook 上那样在 div 外部制作可滚动的 DIV 并带有滚动条?

    我想要一个可滚动的 div 但默认情况下滚动条应该位于浏览器的右侧 但不在 div 的右侧 我在 facebook 上看到过 ceter div contentArea 由右侧浏览器滚动条滚动 Facebook 的做法是让所有不滚动的内容都
  • Rails 单表继承 - 显式设置类型的最佳方法是什么?

    我在用单表继承在我的 Rails 应用程序中 并且想要显式设置实例的类型 我有以下内容 class Event lt ActiveRecord Base class SpecialEvent lt Event 它是通过单表继承来实现的 Sp
  • 我可以使用二进制在汇编中写入整数常量吗?

    我有一个作业要求定义 4 个整数 每个整数都有不同的字节长度 1 2 4 8 这段代码可以工作吗 segment data one db 1 two dw 01 four dd 1011 eight dq 01101110 global s
  • 我可以检测用户何时使用后退按钮进入页面吗?

    Edit 我真正需要知道的是 当用户通过后退按钮到达页面时 是否有任何 javascript 事件能够可靠地触发 我尝试过onload活动为body元素 但它不会在 Firefox 或 Safari 上触发 我正在使用一些旧代码 这些代码试
  • MongoDB E11000重复键错误

    我有一个模型在第一次发布后不断出错 我正在创建一个调度应用程序 该应用程序有 X 天 包含房间和房间的时间段 我遇到的问题是在数据库中创建日期对象 为了便于阅读 我只需要一个键值对 day model js var mongoose req
  • 现代 C++ 方法重复代码设定次数

    很简单 是否有一种更简单的方法来重复一个块一定次数 其中块内部不需要计数器变量 简单的解决方案当然是 for int i 0 i lt repetitions i do your thing i is not used here 然而 既然
  • 全局字典不需要关键字global来修改吗? [复制]

    这个问题在这里已经有答案了 我想知道为什么我可以更改全局字典而不用global关键词 为什么其他类型必须这样做 这背后有什么逻辑吗 例如 代码 usr bin env python3 stringvar mod dictvar key1 1
  • 从返回堆栈恢复的片段

    我正在使用兼容包来将 Fragment 与 Android 2 2 一起使用 当使用片段并将它们之间的转换添加到后台堆栈时 我希望实现与活动的 onResume 相同的行为 即每当片段弹出后被带到 前台 对用户可见 时backstack 我
  • Bash,不同文件测试的令人困惑的结果(test -f)

    我在 bash 中对这个表达式感到困惑 var empty var test f var echo test if such file exists 0 and this file exists amazing test f echo le
  • 在 Django 中合并两个具有相同主键的不相关表/模型

    我有两个具有相同主键的不相关表 ip mac 11 11 11 11 48 C0 09 1F 9B 54 33 33 33 33 4E 10 A3 BC B8 9D 44 44 44 44 CD 00 60 08 56 2A 55 55 5