Как получить список объектов из многозначного поля с помощью SQLAlchemy с использованием ORM?

#python #ms-access #sqlalchemy #orm #sqlalchemy-access

#питон #ms-доступ #sql — алхимия #orm #sqlalchemy-доступ

Вопрос:

У меня есть файл базы данных MS Access (.accdb) от моего клиента, и мне нужно описать таблицы и столбцы с помощью класса declarative_base. Как я вижу в конструкторе таблицы — один из столбцов имеет целочисленное значение и имеет отношение «один ко многим» с другим столбцом в какой-то другой таблице (внешний ключ). Но на самом деле в этом внешнем ключе хранится не одно целое значение, а строка с числовыми значениями, разделенными точками с запятой. Этот метод называется «многозначные поля». На самом деле это отношение «многие ко многим» без ассоциативных таблиц.

Очень упрощенная схема:

 Persons
-------------
id - Integer
name - String
vacancy_id - Integer (multi-value, Foreign key of Vacancies.id)

Vacancies
-------------
id - Integer
vacancy_name - String
 

Я попытался сопоставить классы с таблицами, используя родительский класс declarative_base. Но не могу найти, как объявить отношение «многие ко многим» без ассоциативной таблицы. Теперь у меня есть такой код.

 Base = declarative_base()


class Vacancy(Base):
    __tablename__ = 'Vacancies'
    id = sa.Column(sa.Integer, name='id', primary_key=True, autoincrement=True)
    vacancy_name = sa.Column(sa.String(255), name='vacancy_name')


class Person(Base):
    __tablename__ = 'Persons'
    id = sa.Column(sa.Integer, name='id', primary_key=True, autoincrement=True)
    name = sa.Column(sa.String(255), name='name')
    vacancy_id = sa.Column(sa.Integer, ForeignKey(Vacancy.id), name='vacancy_id')
    vacancies = relationship(Vacancy)
 

Во время запроса Person у меня странное поведение:

  • Если vacancy_id не указан, я получаю Person.vacancies как None.
  • Если vacancy_id указан как единственное значение (т.Е. «1»), в Person.vacancies я получаю один объект класса вакансий.
  • Если vacancy_id указан как множественное значение (т.Е. «1; 2; 3»), в Person.vacancies я также не получаю ни одного.

Конечно, я могу запросить необработанный Person.vacancy_id, разделить его точкой с запятой и сделать запрос на получение вакансий со списком идентификаторов.

Но мне интересно, может ли SQLAlchemy обрабатывать «многозначные поля»? И каков наилучший способ работы с такими файлами?

ОБНОВЛЕНИЕ В настоящее время я сделал следующее обходное решение для автоматического анализа многозначных столбцов. Это должно быть добавлено в класс Persons:

 @orm.reconstructor
def load_on_init(self):
    if self.vacancy_id:
        ids = self.vacancy_id.split(';')
        self.vacancies = [x for x in Vacancy.query.filter(Vacancy.id.in_(ids)).all()]
    else:
        self.vacancies = []
 

Класс вакансий должен иметь атрибут fllowing:

 query = DBSession.query_property()
 

Наконец, мы должны подготовить сеанс для использования в классе:

 engine = create_engine(CONNECTION_URI)
DBSession = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
 

Комментарии:

1. лучший способ — использовать ассоциативную таблицу, или вам нужно будет изменить поле vacancy_id в строке без Foreign Key сохранения идентификаторов с точкой с запятой, а затем программно найти поле Vacancies для заполнения vacancies

2. @WillianVieira Спасибо, но я не могу изменить DB, потому что его структура вне моего контроля. Я работаю с «живой» базой данных. Также я могу анализировать это поле каждый раз, это не проблема. Мой вопрос был — может ли SQLAlchemy обрабатывать «многозначные поля» из коробки?

3. Я голосую за закрытие этого вопроса, потому что пользователь, похоже, нашел решение.

Ответ №1:

Access ODBC предоставляет очень ограниченную поддержку полей поиска с несколькими значениями. Такие поля фактически реализуются с использованием скрытой таблицы ассоциаций (с именем like f_1BC9E55B5578456EB5ACABC99BB2FF0B_vacancies ), но эти таблицы недоступны из операторов SQL:

 SELECT * from f_1BC9E55B5578456EB5ACABC99BB2FF0B_vacancies
 

приводит к ошибке

Компонент Microsoft Access database Engine не может найти входную таблицу или запрос «. Убедитесь, что он существует и что его название написано правильно.

Как вы обнаружили, Access ODBC считывает ключевые значения нескольких записей и представляет их в виде списка, разделенного точкой с запятой, который мы можем проанализировать, но мы не можем обновить эти значения

 UPDATE Persons SET vacancies = '1;2' WHERE id = 1
 

терпит неудачу с

Запрос на ОБНОВЛЕНИЕ или УДАЛЕНИЕ не может содержать многозначное поле. (-3209)

Итак, TL; DR, если вам нужно только чтение из базы данных, то вашего обходного пути может быть достаточно, но если вам нужно изменить эти многозначные поля, то Access ODBC не выполнит эту работу за вас.

Комментарии:

1. Большое спасибо за подробный ответ. Это то, что я искал. Да, я читаю только из базы данных MS Access, не нужно что-то менять. Но мне интересно — почему MS создала многозначные поля, которые не могут быть обновлены через драйвер ODBC? Я знаю, что это очень удобно для выбора нескольких значений в столбце, но отсутствие инструкций по обновлению / вставке поддержки делает это решение практически непригодным.

2. Насколько я понимаю, поля поиска были добавлены в Access в основном для поддержки интеграции с SharePoint. Предположительно, они хотели сделать сложность отношений «многие ко многим» невидимой для конечного пользователя, поэтому они скрыли таблицу ассоциаций. Им нужно было предоставить способ управления отношениями, поэтому они расширили DAO для предоставления Recordset2 объекта. Однако это плохо согласуется с SQL-моделью связанных таблиц, поэтому они не полностью реализовали CRUD в драйвере ODBC или поставщике OLEDB.