SQLAlchemy AmbiguousForeignKeysError - PullRequest
0 голосов
/ 21 марта 2019

Я искал эту ошибку и ничего не мог понять.Я получаю сообщение об ошибке ниже:

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 Ответ

1 голос
/ 21 марта 2019

Пожалуйста, прочитайте о том, как создать минимальный, полный и проверяемый пример . Это может звучать придирчиво, но от этого есть много преимуществ.

Для начала, просто удивительно, как часто вы будете сами придумывать ответ, отсекая проблему в 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.payment - это FK, равный Payment.payment_id, и Payment.sale - это FK, равный Sale.sale_id. Вот почему условие соединения является «неоднозначным» - потому что между двумя таблицами есть два возможных пути соединения.

Укажите аргумент 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...