Django ManyToMany через несколько баз данных

TLTR: Django не поддерживает имена базы данных в SQL-запросах, могу ли я как-то заставить это сделать это или есть обходное решение?

Длинная версия:

У меня есть две устаревшие базы данных MySQL (Примечание: я не влияю на макет БД), для которого я создаю API для чтения только. > с использованием DRF на Django 1.11 и python 3.6

Я работаю над ограничением ссылочной целостности для БД MyISAM, используя поле SpanningForeignKey, предложенное здесь: qaru.site/info/75234/...

Я пытаюсь подключить таблицу из DB1 к таблице из DB2 через ManyToMany через таблицу в DB1. Создание запроса Django:

SELECT "table_b"."id" FROM "table_b" INNER JOIN "throughtable" ON ("table_b"."id" = "throughtable"."b_id") WHERE "throughtable"."b_id" = 12345

Что, конечно, дает мне ошибку "Таблица" DB2.thropable "не существует", потому что сквозной столбец находится в 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 включать имя базы данных в запрос? Или есть ли обходной путь для этого?

Ответ 1

расширенный EDIT

Решение существует для Django 1.6 + (включая 1.11) для MySQL и sqlite, опцией ForeignKey. db_constraint= False и явный Meta.db_table. Если имя базы данных и имя таблицы указаны по '' (для MySQL) или '' '(для других db), например db_table = '"db2"."table2"'), то это не указано больше, а точка не соответствует котировке. Действительные запросы скомпилированы Django ORM. Лучшим аналогичным решением является db_table = 'db2"."table2' (что позволяет не только присоединяться, но и по одной проблеме, которая ближе к пересечению ограничений привязки db)

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'

Этот синтаксический анализ запроса поддерживается всеми db файлами в Django, однако другие необходимые шаги должны обсуждаться отдельно с помощью бэкэнд. Я пытаюсь ответить в более общем плане, потому что я нашел аналогичный важный вопрос.

Параметр "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)

Тогда ему не нужен маршрутизатор базы данных, и нормальный django...ForeignKey может использоваться с db_constraint = False. Преимущество заключается в том, что "db_table" не требуется, если имена таблиц уникальны между базами данных.

В MySQL внешних ключах между различными базами данных легко. Все команды, такие как SELECT, INSERT, DELETE, поддерживают любые имена баз данных, не прикрепляя их ранее.


Этот вопрос касался устаревших баз данных. Однако у меня есть некоторые интересные результаты с миграциями.

Ответ 2

У меня есть аналогичная настройка с PostgreSQL. Использование search_path, чтобы сделать ссылки на кросс-схемы возможными в Django (схема в postgres = database в mysql). К сожалению, похоже, что у MySQL нет такого механизма.

Однако вы можете попробовать свою удачу создать для него виды. Создавайте представления в одной базе данных, которая ссылается на другие базы данных, используйте ее для выбора данных. Я думаю, что это лучший вариант, так как вы хотите, чтобы ваши данные были доступны только для чтения.

Однако не идеальное решение, выполняющее необработанные запросы, может быть более полезным в некоторых случаях.


UPD: Предоставление подробных сведений о моей настройке с помощью PostgreSQL (по запросу позже). Я не мог найти ничего подобного search_path в документации MySQL.

Быстрое введение

PostgreSQL имеет Schemas. Они являются синонимом баз данных MySQL. Поэтому, если вы являетесь пользователем MySQL, образно замените слово "схема" словом "база данных". Запросы могут соединяться между таблицами между схемами, создавать внешние ключи и т.д. Каждый пользователь (роль) имеет search_path:

Эта переменная [search_path] указывает порядок поиска схем, когда объект (таблица, тип данных, функция и т.д.) ссылается на простой имя без указанной схемы.

Особое внимание уделяется "не указанной схеме", потому что именно это делает Django.

Пример: устаревшие базы данных

Скажем, у нас есть схемы устаревших купе, и поскольку нам не разрешено изменять их, мы также хотим, чтобы одна новая схема сохраняла отношение 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 является первым в списке, там будут созданы новые таблицы. Итак, следующий код →

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

- > приведет к миграции, которая создает таблицу throughtable, которая ссылается на old1_table и old2_table.

Проблемы: если у вас было несколько таблиц с одинаковыми именами, вам нужно будет либо переименовать их, либо еще обмануть Django, чтобы использовать точку внутри имен таблиц.