SQLalchemy 找不到用于创建外键的表

Posted

技术标签:

【中文标题】SQLalchemy 找不到用于创建外键的表【英文标题】:SQLalchemy not find table for creating foreign key 【发布时间】:2015-03-18 18:44:06 【问题描述】:

我在使用 SQL Alchemy 时遇到问题,在尝试创建数据库时,我得到:

"sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'estate_agent.person_id' could not find table 'person' with which to generate a foreign key to target column 'id'"

元数据:

db = create_engine('postgresql+psycopg2:...//')
meta = MetaData()
meta.bind = db

人员表:

tbl_person = Table(
   'person', meta,
   Column('id', Integer, Sequence('seq_person_id'), primary_key=True),
   Column('name', String(100), unique=True, nullable = False),
   Column('password', String(40), nullable = False),
   Column('person_type_id', Integer, ForeignKey("person_type.id"), nullable = False),
   Column('register_date', DateTime, default = datetime.now),
   Column('pendencies', String(200)),
   Column('active', Boolean, default = True),
   schema = 'public')

错误表:

tbl_estate_agent = Table(
   'estate_agent', meta,
   Column('person_id', Integer, ForeignKey("person.id"), primary_key = True),
   Column('prize_range_id', Integer, ForeignKey("prize_range.id"), nullable = False),
   schema = 'public')

普通表(正常创建fk)

tbl_person_agent = Table(
   'person_agent', meta,
   Column('person_id', Integer, ForeignKey("person.id"), primary_key = True),
   Column('prize_range_id', Integer, ForeignKey("prize_range.id"), nullable = False),
   schema = 'public')

创建调用:

meta.create_all(checkfirst=True)

完整的错误日志:

Traceback (most recent call last):
   File "database_client.py", line 159, in <module>
    meta.create_all(checkfirst=True)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/schema.py", line 3404, in create_all
    tables=tables)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 1616, in _run_visitor
    conn._run_visitor(visitorcallable, element, **kwargs)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 1245, in _run_visitor
    **kwargs).traverse_single(element)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/visitors.py", line 120, in traverse_single
    return meth(obj, **kw)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/ddl.py", line 699, in visit_metadata
    collection = [t for t in sort_tables(tables)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/ddl.py", line 862, in sort_tables
    'foreign_key': visit_foreign_key)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/visitors.py", line 256, in traverse
    return traverse_using(iterate(obj, opts), obj, visitors)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/visitors.py", line 247, in traverse_using
    meth(target)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/ddl.py", line 853, in visit_foreign_key
    parent_table = fkey.column.table   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/util/langhelpers.py", line 725, in __get__
    obj.__dict__[self.__name__] = result = self.fget(obj)
   File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/sql/schema.py", line 1720, in column tablekey)
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'estate_agent.person_id' could not find table 'person' with which to generate a foreign key to target column 'id'

【问题讨论】:

【参考方案1】:

通过将以下行添加到我的parent 表中解决了我的问题。如果是声明式:

children = relationship("Child")

否则:SQLAlchemy - Classic Mapper

也尝试查看here (SO),可能会有所帮助。

【讨论】:

【参考方案2】:

解决方案是将字符串替换为实际的列:

Column('person_id', Integer, ForeignKey(tbl_person.c.id), primary_key=True)

【讨论】:

@Icyh 迟到了,但它可以工作,因为您必须导入 Table 对象,这意味着它实际上是创建的。我敢打赌,在原始代码中 tbl_person 尚未创建,因此字符串查找失败,因为 MetaData 不包含它。 感谢您的评论.. 救了我的命【参考方案3】:

在声明式的情况下,我通过简单地导入“找不到”的类来解决这个问题。

【讨论】:

【参考方案4】:

结论

这个异常是因为MetaData实例中没有父表的记录,需要在数据库中检索该表。 调用 MetaData 类的函数“reflect”来获取数据库上所有已存在的表。应该这样使用

def upgrade(migrate_engine):

    meta.bind = migrate_engine

    meta.reflect() # <------ Obtain all tables here.

    aggregate_metadata.create()
    aggregate_hosts.create()

说明

一个表与另一个具有关联外键的表位于不同的文件中。 这种情况下,sqlalchemy在建表时会找不到对应的表,如下图:

sqlalchemy.exc.NoReferencedTableError:与列“aggregate_metadata.aggregate_id”关联的外键找不到表“聚合”,用于生成目标列“id”的外键

例如:

# File 002_Add_aggregates_table.py
# ========================================
...

meta = MetaData()

aggregates = Table('aggregates', meta,
    ...
    Column('id', Integer, primary_key=True, nullable=False),
    mysql_engine='InnoDB',
    mysql_charset='utf8'
)

def upgrade(migrate_engine):
    meta.bind = migrate_engine
    aggregates.create()
# File 003_Add_aggregate_metadata_hosts.py
# ========================================
...

meta = MetaData()

aggregate_metadata = Table('aggregate_metadata', meta,
    ...
    Column('aggregate_id', Integer, ForeignKey('aggregates.id'), # <------ ForeignKey
           nullable=False),
    mysql_engine='InnoDB',
    mysql_charset='utf8'
    )

def upgrade(migrate_engine):

    meta.bind = migrate_engine
    aggregate_metadata.create()

根本原因

让我们定位到引发异常的点

  File "/opt/xxx/.local/lib/python3.6/site-packages/sqlalchemy/util/langhelpers.py", line 1113, in __get__
    obj.__dict__[self.__name__] = result = self.fget(obj)
  File "/opt/xxx/.local/lib/python3.6/site-packages/sqlalchemy/sql/schema.py", line 2394, in column
    tablekey,
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'aggregate_metadata.aggregate_id' could not find table 'aggregates' with which to generate a foreign key to target column 'id'

我们可以找到相应的代码并进行调试:

# File: sqlalchemy/sql/schema.py
2358     def column(self):
            ...
2371
2372        if isinstance(self._colspec, util.string_types):
2373
2374           parenttable, tablekey, colname = self._resolve_col_tokens()

# =========> BeginDebug
2375           raise Exception(
2376               'imgrass->\n'
2377               '  - parenttable: %s\n'
2378               '  - parenttable.metadata: %s\n'
2379               '  - tablekey: %s\n'
2380               '  - colname: %s' % (
2381                   parenttable,
2382                   parenttable.metadata,
2383                   tablekey,
2384                   colname
2385                   )
2386               )
# =========> EndDebug

2387
2388           if tablekey not in parenttable.metadata:
2389              raise exc.NoReferencedTableError(
2390                 "Foreign key associated with column '%s' could not find "
2391                 "table '%s' with which to generate a "
2392                 "foreign key to target column '%s'"
2393                 % (self.parent, tablekey, colname),
2394                 tablekey,
2395              )

然后我们可以得到以下异常:

Exception: imgrass->
  - parenttable: aggregate_metadata
  - parenttable.metadata: MetaData(bind=Engine(mysql+pymysql://imgrass:***@172.17.0.1/demo))
  - tablekey: aggregates
  - colname: id

所以,parenttable.metadata 是类MetaData 的一个实例,tablekey 是一个表名。 我们可以合理地猜测表 aggregates 应该包含在类 MetaData 的实例中。 考虑到这张表的定义在另一个文件中,而MetaData实例有DB(bind=xxx)的连接方式,所以MetaData类中应该有一个函数获取数据库中的所有表。

在元数据中,我们可以找到这个函数

# File: sqlalchemy/sql/schema.py

class MetaData(SchemaItem):
    ...

    def reflect(...):
        r"""Load all available table definitions from the database.
        ...

从它的描述中,我们可以猜出它的作用,让我们将它应用到我的脚本中:

# File 003_Add_aggregate_metadata_hosts.py
# ========================================
...

def upgrade(migrate_engine):

    meta.bind = migrate_engine

# ==================> BeginUpdate
    meta.reflect()
# ==================> EndUpdate

    aggregate_metadata.create()
    aggregate_hosts.create()

没关系!

【讨论】:

【参考方案5】:

虽然投票最多的答案解决了这个问题,但用对象替换字符串会迫使您以特定的顺序定义表(这对于非常大的数据库来说可能很重要)。来自SQLAlchemy docs:

使用字符串的好处是 [不同表] 之间的 in-python 链接仅在第一次需要时才被解析,因此表对象可以轻松地分布在多个模块中并以任意顺序定义。

您可以通过将架构传递给ForeignKey 来继续使用字符串。例如,不要这样做:

tbl_estate_agent = Table(
   'estate_agent', meta,
   Column('person_id', Integer, ForeignKey("person.id"), primary_key = True),
   Column('prize_range_id', Integer, ForeignKey("prize_range.id"), nullable = False),
   schema = 'public')

可以做到:

tbl_estate_agent = Table(
   'estate_agent', meta,
   Column('person_id', Integer, ForeignKey("public.person.id"), primary_key = True),
   Column('prize_range_id', Integer, ForeignKey("public.prize_range.id"), nullable = False),
   schema = 'public')

【讨论】:

以上是关于SQLalchemy 找不到用于创建外键的表的主要内容,如果未能解决你的问题,请参考以下文章

Sql Server:创建具有多个外键的表

sql 删除带外键约束的表的语句是啥

SQLAlchemy外键的使用

mysql外键

尝试添加外键的 SQLAlchemy 错误

sqlalchemy中复合外键的多个关系