0

I have MS Access DB file (.accdb) from my client and need to describe tables and columns with declarative_base class. As I can see in table constructor - one of column has Integer value and has relationship "one-to-many" with another column in some another table (foreign key). But actually in this foreign key stored not single Integer value, but string with number values separated with semicolons. This technique called as "multi-value fields". In fact this is "many-to-many" relationship without associative tables.

Very simplified scheme:

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

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

I tried to map classes to tables using declarative_base parent class. But can't find how to declare "many-to-many" relationship without associative table. Now I have such code.

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)

During request Person I have strange behavior:

  • If vacancy_id not specified, I get Person.vacancies as None.
  • If vacancy_id specified as single value (i.e. "1"), in Person.vacancies I get single object of Vacancy class.
  • If vacancy_id specified as multiple value (i.e. "1;2;3"), in Person.vacancies I also get None.

Of course I can request raw Person.vacancy_id, split it with semicolon, and make request to get Vacancies with list of ID's.

But I wonder - if SqlAlchemy can process "multi-value fields"? And what the best way to work with such fileds?

UPDATE At present I made following workaround to automatically parse multi-value columns. This should be added to Persons class:

@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 = []

Vacancies class should have fllowing attribute:

query = DBSession.query_property()

Finally we have to prepare session for in-class usage:

engine = create_engine(CONNECTION_URI)
DBSession = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
Yuri
  • 135
  • 10
  • the best way is using associative table, or you will need change the field `vacancy_id` in a string without `Foreign Key` to store ids with semicolon, and then programmatically find the `Vacancies` to fill `vacancies` field – Willian Vieira Jan 15 '21 at 20:15
  • @WillianVieira Thanks, but I can't change DB because its structure out of my control. I work with "live" database. Also I can parse this field each time, it is not a problem. My question was - if SqlAlchemy can process "multi-value fields" out-of-box? – Yuri Jan 15 '21 at 20:22
  • I’m voting to close this question because user appears to have found solution. – June7 Jan 15 '21 at 22:40

1 Answers1

2

Access ODBC provides very limited support for multi-value lookup fields. Such fields are actually implemented using a hidden association table (with a name like f_1BC9E55B5578456EB5ACABC99BB2FF0B_vacancies) but those tables are not accessible from SQL statements:

SELECT * from f_1BC9E55B5578456EB5ACABC99BB2FF0B_vacancies

results in the error

The Microsoft Access database engine cannot find the input table or query ''. Make sure it exists and that its name is spelled correctly.

As you have discovered, Access ODBC will read the key values of the multiple entries and present them as a semicolon-separated list that we can parse, but we cannot update those values

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

fails with

An UPDATE or DELETE query cannot contain a multi-valued field. (-3209)

So, TL;DR, if you only need to read from the database then your workaround may be sufficient, but if you need to modify those multi-valued fields then Access ODBC is not going to get the job done for you.

Gord Thompson
  • 116,920
  • 32
  • 215
  • 418
  • Many thanks for detailed answer. This is what I looked for. Yes, I'm only reading from MS Access DB, no need to change something. But I'm wondering - why MS made multi-valued fields which cannot be updated via ODBC driver? I know this is very handy to select multiple values in column, but lack of support UPDATE/INSERT instructions makes this decision almost unusable. – Yuri Jan 16 '21 at 06:23
  • 1
    My understanding is that lookup fields were added to Access mainly to support integration with SharePoint. Presumably they wanted to make the complexity of the many-to-many relationship invisible to the end-user so they hid the association table. They needed to provide a way to manipulate the relationship so they extended DAO to provide the `Recordset2` object. However, that doesn't fit well with the SQL model of relating tables so they didn't fully implement CRUD in the ODBC driver or the OLEDB provider. – Gord Thompson Jan 16 '21 at 19:15