Django ManyToMany通过多个数据库

mas*_*oda 12 python mysql django django-database

TLTR: Django 在SQL查询中不包含数据库名称,我可以以某种方式强制它执行此操作还是有解决方法?

长版:

我有两个遗留的 MySQL 数据库(注意:我对数据库布局没有影响),我正在使用Django 1.11和python 3.6上的DRF 创建一个只读API

我正在使用此处建议的SpanningForeignKey字段来解决MyISAM DB的参照完整性限制:https://stackoverflow.com/a/32078727/7933618

我正在尝试通过MultiToMany通过DB1上的表将DB1中的表连接到DB2中的表.这是Django正在创建的查询:

SELECT "table_b"."id" FROM "table_b" INNER JOIN "throughtable" ON ("table_b"."id" = "throughtable"."b_id") WHERE "throughtable"."b_id" = 12345
Run Code Online (Sandbox Code Playgroud)

这当然给了我一个错误"表'DB2.throughtable'不存在",因为viatable在DB1上,我不知道如何强制Django使用DB名称为表添加前缀.查询应该是:

SELECT table_b.id FROM DB2.table_b INNER JOIN DB1.throughtable ON (table_b.id = throughtable.b_id) WHERE throughtable.b_id = 12345
Run Code Online (Sandbox Code Playgroud)

app1的 型号db1_app/models.py:(DB1)

class TableA(models.Model):
    id = models.AutoField(primary_key=True)
    # some other fields
    relations = models.ManyToManyField(TableB, through='Throughtable')

class Throughtable(models.Model):
    id = models.AutoField(primary_key=True)
    a_id = models.ForeignKey(TableA, to_field='id')
    b_id = SpanningForeignKey(TableB, db_constraint=False, to_field='id')
Run Code Online (Sandbox Code Playgroud)

app2的 模型db2_app/models.py:(DB2)

class TableB(models.Model):
    id = models.AutoField(primary_key=True)
    # some other fields
Run Code Online (Sandbox Code Playgroud)

数据库路由:

def db_for_read(self, model, **hints):
    if model._meta.app_label == 'db1_app':
        return 'DB1'

    if model._meta.app_label == 'db2_app':
        return 'DB2'

    return None
Run Code Online (Sandbox Code Playgroud)

我可以强制Django 在查询中包含数据库名称吗?或者有任何解决方法吗?

hyn*_*cer 8

广泛的编辑

对于MySQLsqlite后端,Django 1.6+(包括1.11)存在一个解决方案,可选择ForeignKey.db_constraint = False和explicit .如果数据库名和表名被引用的"'"(用于MySQL的)或者"""(对于其他数据库),例如),然后它不引用更多的点是出报价.有效的查询被编译Django ORM.一个更好的类似解决方案是(不仅允许连接,而且它也是一个更接近跨数据库约束迁移的问题)Meta.db_tabledb_table = '"db2"."table2"'db_table = 'db2"."table2'

db2_name = settings.DATABASES['db2']['NAME']

class Table1(models.Model):
    fk = models.ForeignKey('Table2', on_delete=models.DO_NOTHING, db_constraint=False)

class Table2(models.Model):
    name = models.CharField(max_length=10)
    ....
    class Meta:    
        db_table = '`%s`.`table2`' % db2_name  # for MySQL
        # db_table = '"db2"."table2"'          # for all other backends
        managed = False
Run Code Online (Sandbox Code Playgroud)

查询集:

>>> qs = Table2.objects.all()
>>> str(qs.query)
'SELECT "DB2"."table2"."id" FROM DB2"."table2"'
>>> qs = Table1.objects.filter(fk__name='B')
>>> str(qs.query)
SELECT "app_table1"."id"
    FROM "app_table1"
    INNER JOIN "db2"."app_table2" ON ( "app_table1"."fk_id" = "db2"."app_table2"."id" )
    WHERE "db2"."app_table2"."b" = 'B'
Run Code Online (Sandbox Code Playgroud)

Django中的所有db后端都支持该查询解析,但其他必要步骤必须由后端单独讨论.我试图更广泛地回答,因为我发现了一个类似的重要问题.

选项'db_constraint'是迁移所必需的,因为Django无法创建引用完整性约束
ADD foreign key table1(fk_id) REFERENCES db2.table2(id),
可以为MySQL 手动创建它.

特定后端的问题是,是否可以在运行时将另一个数据库连接到缺省值,并且是否支持跨数据库外键.这些模型也是可写的.间接连接的数据库应该用作遗留数据库managed=False(因为只有一个django_migrations用于迁移跟踪的表只在直接连接的数据库中创建.此表应该只描述同一数据库中的表.)但是,外键的索引可以自动创建如果数据库系统支持此类索引,则在托管端.

Sqlite3:它必须在运行时附加到另一个默认的sqlite3数据库(回答SQLite - 如何连接来自不同数据库的表),最好是信号connection_created:

from django.db.backends.signals import connection_created

def signal_handler(sender, connection, **kwargs):
    if connection.alias == 'default' and connection.vendor == 'sqlite':
        cur = connection.cursor()
        cur.execute("attach '%s' as db2" % db2_name)
        # cur.execute("PRAGMA foreign_keys = ON")  # optional

connection_created.connect(signal_handler)
Run Code Online (Sandbox Code Playgroud)

然后它当然不需要数据库路由器,并且正常django...ForeignKey可以与db_constraint = False一起使用.一个优点是,如果表名在数据库之间是唯一的,则不需要"db_table".

MySQL中 ,不同数据库之间的外键很容易.所有命令(如SELECT,INSERT,DELETE)都支持任何数据库名称,而不会先附加它们.


这个问题是关于遗留数据库的.然而,我对迁移也有一些有趣的结果.


Art*_*Art 6

我有一个与PostgreSQL类似的设置.利用search_pathDjango中的跨模式引用(postgres中的模式= mysql中的数据库).不幸的是,似乎MySQL没有这样的机制.

但是,您可以尝试为它创建视图.在一个引用其他数据库的数据库中创建视图,使用它来选择数据.我认为这是最好的选择,因为无论如何你都希望你的数据是只读的.

然而,它不是一个完美的解决方案,在某些情况下执行原始查询可能更有用.


UPD:提供有关我使用PostgreSQL进行设置的模式详细信息(稍后由bounty提出请求).我search_path在MySQL文档中找不到任何东西.

快速介绍

PostgreSQL有Schema.它们是MySQL数据库的同义词.因此,如果您是MySQL用户,则想象性地将单词"schema"替换为单词"database".请求可以在模式之间连接表,创建外键等...每个用户(角色)都有一个search_path:

此变量[search_path]指定在未指定架构的简单名称引用对象(表,数据类型,函数等)时搜索架构的顺序.

特别注意"没有指定架构",因为这正是Django所做的.

示例:旧数据库

假设我们得到了coupe遗留模式,并且由于我们不允许修改它们,我们还需要一个新的模式来存储NM关系.

  • old1它是第一个传统模式,它有old1_table(为了方便起见,它也是模型名称)
  • old2 它是第二个传统架构 old2_table
  • django_schema 是一个新的,它将存储所需的NM关系

我们需要做的就是:

alter role django_user set search_path = django_schema, old1, old2;
Run Code Online (Sandbox Code Playgroud)

就是这个.是的,那很简单.Django没有在任何地方指定的模式("数据库")的名称.Django实际上不知道发生了什么,一切都是由幕后的PostgreSQL管理的.由于django_schema是列表中的第一个,因此将在那里创建新表.所以下面的代码 - >

class Throughtable(models.Model):
    a_id = models.ForeignKey('old1_table', ...)
    b_id = models.ForeignKey('old2_table', ...)
Run Code Online (Sandbox Code Playgroud)

- >将导致创建throughtable引用old1_table和的表的迁移old2_table.

问题:如果您碰巧有几个具有相同名称的表,您将需要重命名它们或仍然欺骗Django使用表名内的点.