Django ManyToMany通过多个数据库

时间:2017-07-10 17:28:10

标签: python mysql django django-database

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

长版:

我有两个传统 MySQL 数据库(注意:我对数据库布局没有影响)我为创建一个只读API < / strong>在Django 1.11和python 3.6上使用DRF

我使用此处建议的SpanningForeignKey字段解决MyISAM数据库的参照完整性限制问题:https://stackoverflow.com/a/32078727/7933618

我试图通过DB1上的表通过ManyToMany将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

这当然给了我一个错误&#34;表&#39; DB2.throughtable&#39;不存在&#34;因为贯穿式是在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

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')

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

class TableB(models.Model):
    id = models.AutoField(primary_key=True)
    # some other fields

数据库路由器

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

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

3 个答案:

答案 0 :(得分:8)

广泛的编辑

Django 1.6 + (包括1.11)针对 MySQL sqlite 后端的解决方案,通过选项ForeignKey.db_constraint=False和显式的 Meta.db_table 即可。如果数据库名称和表名是引用由'''(对于MySQL)或'''(对于其他数据库),例如db_table = '"db2"."table2"')。那么它没有引用更多和有点查询是由Django ORM编译的。更好的类似解决方案是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

查询集:

>>> 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'

Django中的所有数据库后端支持该查询解析,但其他必要步骤必须由后端单独讨论。我试图更广泛地回答,因为我找到了similar important question

选项'db_constraint'是迁移所必需的,因为Django无法创建参考完整性约束
ADD foreign key table1(fk_id) REFERENCES db2.table2(id)
但它为MySQL can be created manually

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

Sqlite3 :它必须在运行时附加到另一个默认的sqlite3数据库(回答SQLite - How do you join tables from different databases),最好是信号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)

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

MySQL foreign keys between different databases中很容易。 SELECT,INSERT,DELETE等所有命令都支持任何数据库名称,而不会先附加它们。

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

答案 1 :(得分:6)

我有与 PostgreSQL 类似的设置。利用search_path在Django中实现跨模式引用(postgres中的模式= mysql中的数据库)。不幸的是,似乎MySQL没有这样的机制。

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

然而,这不是一个完美的解决方案,在某些情况下执行raw queries可能会更有用。

UPD:使用PostgreSQL提供有关我的设置的模式详细信息(稍后由bounty请求)。我在MySQL文档中找不到像search_path这样的东西。

快速介绍

PostgreSQL有Schemas。它们是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;

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

class Throughtable(models.Model):
    a_id = models.ForeignKey('old1_table', ...)
    b_id = models.ForeignKey('old2_table', ...)

- &GT;将导致创建引用throughtableold1_table的表old2_table的迁移。

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

答案 2 :(得分:3)

Django确实能够使用多个数据库。请参阅https://docs.djangoproject.com/en/1.11/topics/db/multi-db/

您还可以在Django中使用原始SQL查询。见https://docs.djangoproject.com/en/1.11/topics/db/sql/