SQLAlchemy AmbiguousForeignKeysError

时间:2019-03-21 10:51:48

标签: python sqlalchemy

我已经搜索了此错误,无法理解。我收到以下错误:

  

sqlalchemy.exc.AmbiguousForeignKeysError:无法确定关系Sale.payments的父/子表之间的联接条件-有多个链接表的外键路径。指定'foreign_keys'参数,并提供这些列的列表,这些列应被视为包含对父表的外键引用。

这是我的代码:

# -*- coding: utf-8 -*-

import sqlalchemy as sa
import bcrypt as bc
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
from sqlalchemy_utils import database_exists, create_database

engine = sa.create_engine('sqlite:///data/db/nestopol.db')
if not database_exists(engine.url):
    create_database(engine.url)

session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()


class Staff(Base):

    __tablename__ = 'staffs'

    staff_id = sa.Column(sa.Integer, primary_key=True)
    username = sa.Column(sa.String(64), unique=True, index=True, nullable=False)

    # Needs a length if MySQL is used
    # password is 93 in length
    password = sa.Column(sa.String(124), nullable=False)
    admin = sa.Column(sa.Boolean, default=False, nullable=False)
    first_name = sa.Column(sa.String(26), nullable=False)
    last_name = sa.Column(sa.String(26), nullable=False)
    gender = sa.Column(sa.String(6), nullable=False)
    birthday = sa.Column(sa.String(11), nullable=False)
    mobile_number = sa.Column(sa.String(14), nullable=False)
    city = sa.Column(sa.String(26), nullable=False)
    state = sa.Column(sa.String(26), nullable=False)
    country = sa.Column(sa.String(26), nullable=False)
    address = sa.Column(sa.String(128), nullable=False)
    added_on = sa.Column(sa.DateTime)
    modified_on = sa.Column(sa.DateTime)
    customers = relationship('Customer', backref='staffs', lazy='select')
    products = relationship('Product', backref='staffs', lazy='select')
    categories = relationship('Category', backref='staffs', lazy='select')
    suppliers = relationship('Supplier', backref='staffs', lazy='select')
    supply = relationship('Supply', backref='staffs', lazy='select')
    sales = relationship('Sale', backref='staffs', lazy='select')
    items = relationship('Item', backref='staffs', lazy='select')
    payments = relationship('Payment', backref='staffs', lazy='select')

    def generate_password_hash(self, password):
        return bc.hashpw(self.password, bc.gensalt())

    def check_password_hash(self, plain_text_password, password):
        return bc.checkpw(plain_text_password, self.hashed_password)


class Customer(Base):

    __tablename__ = 'customers'

    customer_id = sa.Column(sa.Integer, primary_key=True)
    first_name = sa.Column(sa.String(26), nullable=False)
    last_name = sa.Column(sa.String(26), nullable=False)
    email = sa.Column(sa.String(64), unique=True, index=True, nullable=False)
    gender = sa.Column(sa.String(7), nullable=False)
    birthday = sa.Column(sa.String(11), nullable=False)
    phone = sa.Column(sa.String(14), nullable=False)
    city = sa.Column(sa.String(26), nullable=False)
    state = sa.Column(sa.String(26), nullable=False)
    country = sa.Column(sa.String(26), nullable=False)
    address = sa.Column(sa.String(128), nullable=False)
    description = sa.Column(sa.String(128), nullable=False)
    purchases = sa.Column(sa.Integer, nullable=False)
    expenditure = sa.Column(sa.Float, nullable=False)
    reward = sa.Column(sa.Integer, nullable=False)
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)
    modified_on = sa.Column(sa.DateTime)
    sales = relationship('Sale', backref='customers', lazy='select')
    items = relationship('Item', backref='customers', lazy='select')
    payments = relationship('Payment', backref='customers', lazy='select')



class Product(Base):

    __tablename__ = 'products'

    product_id = sa.Column(sa.Integer, primary_key=True)
    code = sa.Column(sa.String(128), unique=True, index=True, nullable=False)
    name = sa.Column(sa.String(26), nullable=False)
    category = sa.Column(sa.Integer, sa.ForeignKey('categories.product_category_id'))
    cost_price = sa.Column(sa.Float, nullable=False)
    selling_price = sa.Column(sa.Float, nullable=False)
    weight = sa.Column(sa.Float, nullable=False)
    stock = sa.Column(sa.Integer, nullable=False)
    discount = sa.Column(sa.Float, nullable=False)
    expires = sa.Column(sa.DateTime)
    supplier = sa.Column(sa.Integer, sa.ForeignKey('suppliers.supplier_id'))
    description = sa.Column(sa.String(128), nullable=False)
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)
    items = relationship('Item', backref='products', lazy='select')


class Category(Base):

    __tablename__ = 'categories'

    product_category_id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(26), nullable=False)
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)
    products = relationship('Product', backref='categories', lazy='select')


class Supplier(Base):

    __tablename__ = 'suppliers'

    supplier_id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(26), nullable=False)
    debt = sa.Column(sa.Float, nullable=False)
    email = sa.Column(sa.String(64), unique = True, index=True, nullable=False)
    phone = sa.Column(sa.String(14), nullable=False)
    city = sa.Column(sa.String(26), nullable=False)
    state = sa.Column(sa.String(26), nullable=False)
    country = sa.Column(sa.String(26), nullable=False)
    address = sa.Column(sa.Float, nullable=False)
    description = sa.Column(sa.String(200), nullable=False)
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)
    modified_on = sa.Column(sa.DateTime)
    products = relationship('Product', backref='suppliers', lazy='select')
    supplies = relationship('Supply', backref='suppliers', lazy='select')


class Supply(Base):

    __tablename__ = 'supplies'

    supply_id = sa.Column(sa.Integer, primary_key=True)
    title = sa.Column(sa.String(26), nullable=False)
    supplier = sa.Column(sa.Integer, sa.ForeignKey('suppliers.supplier_id'))
    items = sa.Column(sa.Integer, nullable=False)
    value = sa.Column(sa.Float, nullable=False)
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)

class Sale(Base):

    __tablename__ = 'sales'

    sale_id = sa.Column(sa.Integer, primary_key=True)
    code = sa.Column(sa.String(8), unique=True, index=True, nullable=False)
    title = sa.Column(sa.String(26), nullable=False)
    customer = sa.Column(sa.Integer, sa.ForeignKey('customers.customer_id'))
    total = sa.Column(sa.Float, nullable=False)
    discount = sa.Column(sa.Float, nullable=False)
    vat = sa.Column(sa.Float, nullable=False)
    payment_method = sa.Column(sa.String(26), nullable=False)
    payment = sa.Column(sa.Integer, sa.ForeignKey('payments.payment_id'))
    state = sa.Column(sa.String(26), nullable=False)
    item = sa.Column(sa.Integer, sa.ForeignKey('items.item_id'))
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)
    payments = relationship('Payment', backref='sales', lazy='select')
    items = relationship('Item', backref='sales', lazy='select')

class Item(Base):

    __tablename__ = 'items'

    item_id = sa.Column(sa.Integer, primary_key=True)
    sale = sa.Column(sa.Integer, sa.ForeignKey('sales.sale_id'))
    customer = sa.Column(sa.Integer, sa.ForeignKey('customers.customer_id'))
    item = sa.Column(sa.Integer, sa.ForeignKey('products.product_id'))
    quantity = sa.Column(sa.Integer, nullable=False)
    total = sa.Column(sa.Float, nullable=False)
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)
    sales = relationship('Sale', backref='items', lazy='select')


class Payment(Base):

    __tablename__ = 'payments'

    payment_id = sa.Column(sa.Integer, primary_key=True)
    sale = sa.Column(sa.Integer, sa.ForeignKey('sales.sale_id'))
    customer = sa.Column(sa.Integer, sa.ForeignKey('customers.customer_id'))
    status = sa.Column(sa.String(26), nullable=False)
    amount_paid = sa.Column(sa.Float, nullable=False)
    amount_due = sa.Column(sa.Float, nullable=False)
    comment = sa.Column(sa.String(128), nullable=False)
    author = sa.Column(sa.Integer, sa.ForeignKey('staffs.staff_id'))
    added_on = sa.Column(sa.DateTime)
    modified_on = sa.Column(sa.DateTime)
    sales = relationship('Sale', backref='payments', lazy='select')



Base.metadata.create_all(engine)

请问我该如何解决

1 个答案:

答案 0 :(得分:1)

请阅读有关如何创建Minimal, Complete and Verifiable Example的信息。听起来可能有些挑剔,但这样做有很多好处。

对于初学者来说,将问题简化为MCVE时,您经常会自己找出答案的频率令人惊讶。而且,在几秒钟内,其他人可以很轻松地为您提供帮助。

为使您对区别有所了解,不包括导入,您粘贴了181行代码(不会重现该问题)以表示可以在13行中准确重现的问题(还请注意,您已经使用过backref,我已经修改为back_populates,则需要研究两者之间的区别):

class Sale(Base):
    __tablename__ = 'sales'
    sale_id = sa.Column(sa.Integer, primary_key=True)
    payment = sa.Column(sa.Integer, sa.ForeignKey('payments.payment_id'))
    payments = relationship('Payment', back_populates='sales', lazy='select')

class Payment(Base):
    __tablename__ = 'payments'
    payment_id = sa.Column(sa.Integer, primary_key=True)
    sale = sa.Column(sa.Integer, sa.ForeignKey('sales.sale_id'))
    sales = relationship('Sale', back_populates='payments', lazy='select')

Sale()

运行该代码时出现错误:

sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between
parent/child tables on relationship Sale.payments - there are multiple foreign key 
paths linking the tables.  Specify the 'foreign_keys' argument, providing a list of 
those columns which should be counted as containing a foreign key reference to the 
parent table.

让我们分解异常消息:

  

无法确定上的父/子表之间的连接条件   关系Sale.payments-有多个外键路径   链接表格。

这是在告诉您什么地方出了问题。 Sqlalchemy不知道如何创建关系Sale.payments。这是因为,如果未提供显式联接条件,则sqlalchemy会在表之间寻找外键作为指导。在这种情况下,有两个链接表的外键,Sale.paymentPayment.payment_id的FK,而Payment.saleSale.sale_id的FK。这就是联接条件“含糊”的原因-因为两个表之间有两个潜在的联接路径。

  

指定“ foreign_keys”参数,并提供这些参数的列表   应视为包含外键引用的列   到父表。

这告诉您如何解决问题。我们可以这样做,它将起作用:

class Sale(Base):
    __tablename__ = 'sales'
    sale_id = sa.Column(sa.Integer, primary_key=True)
    payment = sa.Column(sa.Integer, sa.ForeignKey('payments.payment_id'))
    payments = relationship('Payment', back_populates='sales', lazy='select',
                            foreign_keys=[payment])

class Payment(Base):
    __tablename__ = 'payments'
    payment_id = sa.Column(sa.Integer, primary_key=True)
    sale = sa.Column(sa.Integer, sa.ForeignKey('sales.sale_id'))
    sales = relationship('Sale', back_populates='payments', lazy='select',
                         foreign_keys=[Sale.payment])

但是我认为这里的真正问题是两个外键不是必需的。无需循环FK,此代码即可达到完全相同的结果:

class Sale(Base):
    __tablename__ = 'sales'
    sale_id = sa.Column(sa.Integer, primary_key=True)
    payments = relationship('Payment', back_populates='sales', lazy='select')

class Payment(Base):
    __tablename__ = 'payments'
    payment_id = sa.Column(sa.Integer, primary_key=True)
    sale = sa.Column(sa.Integer, sa.ForeignKey('sales.sale_id'))
    sales = relationship('Sale', back_populates='payments', lazy='select')

现在循环FK引用不见了,两个表之间只有一条FK路径,而sqlalchemy可以轻松推断出该关系的正确连接路径,我们不需要指定foreign_keys参数