SQLAlchemy 是否可以添加新记录并自动处理表之间的关系,而无需我手动检查 PK 唯一性?

发布于 2025-01-03 10:20:12 字数 2453 浏览 1 评论 0原文

我是 SQLAlchemy 的新手,并且已经阅读了基本文档。我目前正在关注 Mike Driscoll 的 MediaLocker教程并为了我自己的目的修改/扩展它。

我有三张表(loans、people、card)。卡到贷款和人到贷款都是一对多关系,并且建模如下:

from sqlalchemy import Table, Column, DateTime, Integer, ForeignKey, Unicode
from sqlalchemy.orm import backref, relation
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine("sqlite:///cardsys.db", echo=True)
DeclarativeBase = declarative_base(engine)
metadata = DeclarativeBase.metadata

class Loan(DeclarativeBase):
    """
    Loan model
    """

    __tablename__ = "loans"

    id = Column(Integer, primary_key=True)
    card_id = Column(Unicode, ForeignKey("cards.id"))
    person_id = Column(Unicode, ForeignKey("people.id"))
    date_issued = Column(DateTime)
    date_due = Column(DateTime)
    date_returned = Column(DateTime)
    issue_reason = Column(Unicode(50))
    person = relation("Person", backref="loans", cascade_backrefs=False)
    card = relation("Card", backref="loans", cascade_backrefs=False)

class Card(DeclarativeBase):
    """
    Card model
    """

    __tablename__ = "cards"

    id = Column(Unicode(50), primary_key=True)
    active = Column(Boolean)

class Person(DeclarativeBase):
    """
    Person model
    """

    __tablename__ = "people"

    id = Column(Unicode(50), primary_key=True)
    fname = Column(Unicode(50))
    sname = Column(Unicode(50))

当我尝试创建新贷款(在我的控制器中使用以下方法)时,它对于独特的卡和人来说效果很好,但是一旦我尝试要为特定的人或卡添加第二笔贷款,它会给我一个“非唯一”错误。显然它不是唯一的,这就是重点,但我认为 SQLAlchemy 会为我处理幕后的事情,并添加正确的现有人员或卡 ID 作为新贷款中的 FK,而不是尝试创建新的人员和卡片记录。我是否需要查询数据库来检查 PK 唯一性并手动处理这个问题?我的印象是这应该是 SQLAlchemy 能够自动处理的事情?

def addLoan(session, data):

    loan = Loan()
    loan.date_due = data["loan"]["date_due"]
    loan.date_issued = data["loan"]["date_issued"]
    loan.issue_reason = data["loan"]["issue_reason"]

    person = Person()
    person.id = data["person"]["id"]
    person.fname = data["person"]["fname"]
    person.sname = data["person"]["sname"]
    loan.person = person

    card = Card()
    card.id = data["card"]["id"]
    loan.card = card

    session.add(loan)
    session.commit()

在 MediaLocker 示例中,新行是使用自动增量 PK 创建的(即使对于重复项,也不符合规范化规则)。我想要一个规范化的数据库(即使在一个小项目中,只是为了学习的最佳实践),但在网上找不到任何示例来学习。

如何实现上述目标?

I am a relative newcomer to SQLAlchemy and have read the basic docs. I'm currently following Mike Driscoll's MediaLocker tutorial and modifying/extending it for my own purpose.

I have three tables (loans, people, cards). Card to Loan and Person to Loan are both one-to-many relationships and modelled as such:

from sqlalchemy import Table, Column, DateTime, Integer, ForeignKey, Unicode
from sqlalchemy.orm import backref, relation
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine("sqlite:///cardsys.db", echo=True)
DeclarativeBase = declarative_base(engine)
metadata = DeclarativeBase.metadata

class Loan(DeclarativeBase):
    """
    Loan model
    """

    __tablename__ = "loans"

    id = Column(Integer, primary_key=True)
    card_id = Column(Unicode, ForeignKey("cards.id"))
    person_id = Column(Unicode, ForeignKey("people.id"))
    date_issued = Column(DateTime)
    date_due = Column(DateTime)
    date_returned = Column(DateTime)
    issue_reason = Column(Unicode(50))
    person = relation("Person", backref="loans", cascade_backrefs=False)
    card = relation("Card", backref="loans", cascade_backrefs=False)

class Card(DeclarativeBase):
    """
    Card model
    """

    __tablename__ = "cards"

    id = Column(Unicode(50), primary_key=True)
    active = Column(Boolean)

class Person(DeclarativeBase):
    """
    Person model
    """

    __tablename__ = "people"

    id = Column(Unicode(50), primary_key=True)
    fname = Column(Unicode(50))
    sname = Column(Unicode(50))

When I try to create a new loan (using the below method in my controller) it works fine for unique cards and people, but once I try to add a second loan for a particular person or card it gives me a "non-unique" error. Obviously it's not unique, that's the point, but I thought SQLAlchemy would take care of the behind-the-scenes stuff for me, and add the correct existing person or card id as the FK in the new loan, rather than trying to create new person and card records. Is it up to me to query to the db to check PK uniqueness and handle this manually? I got the impression this should be something SQLAlchemy might be able to handle automatically?

def addLoan(session, data):

    loan = Loan()
    loan.date_due = data["loan"]["date_due"]
    loan.date_issued = data["loan"]["date_issued"]
    loan.issue_reason = data["loan"]["issue_reason"]

    person = Person()
    person.id = data["person"]["id"]
    person.fname = data["person"]["fname"]
    person.sname = data["person"]["sname"]
    loan.person = person

    card = Card()
    card.id = data["card"]["id"]
    loan.card = card

    session.add(loan)
    session.commit()

In the MediaLocker example new rows are created with an auto-increment PK (even for duplicates, not conforming to normalisation rules). I want to have a normalised database (even in a small project, just for best practise in learning) but can't find any examples online to study.

How can I achieve the above?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

风为裳 2025-01-10 10:20:12

在尝试添加具有重复主键的新对象之前,您需要检索现有的 PersonCard 对象并将其分配给关系。您可以通过对代码进行一些小的更改来实现此目的。

def addLoan(session, data):

    loan = Loan()
    loan.date_due = data["loan"]["date_due"]
    loan.date_issued = data["loan"]["date_issued"]
    loan.issue_reason = data["loan"]["issue_reason"]

    person = session.query(Person).get(data["person"]["id"])
    if not person:
        person = Person()
        person.id = data["person"]["id"]
        person.fname = data["person"]["fname"]
        person.sname = data["person"]["sname"]
    loan.person = person

    card = session(Card).query.get(data["card"]["id"])
    if not card:
        card = Card()
        card.id = data["card"]["id"]
    loan.card = card

    session.add(loan)
    session.commit()

还有一些针对 get_or_create 函数的解决方案,如果您想将其合并为一个步骤。

如果您从头开始将大量记录加载到新数据库中,并且您的查询比 get 更复杂(会话对象应该缓存 get 查询) ),您可以通过 ID 将每个新的 Person 和 Card 对象添加到临时字典中,并检索那里的现有对象而不是访问数据库,从而完全避免以内存为代价的查询。

It's up to you to retrieve and assign the existing Person or Card object to the relationship before attempting to add a new one with a duplicate primary key. You can do this with a couple of small changes to your code.

def addLoan(session, data):

    loan = Loan()
    loan.date_due = data["loan"]["date_due"]
    loan.date_issued = data["loan"]["date_issued"]
    loan.issue_reason = data["loan"]["issue_reason"]

    person = session.query(Person).get(data["person"]["id"])
    if not person:
        person = Person()
        person.id = data["person"]["id"]
        person.fname = data["person"]["fname"]
        person.sname = data["person"]["sname"]
    loan.person = person

    card = session(Card).query.get(data["card"]["id"])
    if not card:
        card = Card()
        card.id = data["card"]["id"]
    loan.card = card

    session.add(loan)
    session.commit()

There are also some solutions for get_or_create functions, if you want to wrap it into one step.

If you're loading large numbers of records into a new database from scratch, and your query is more complex than a get (the session object is supposed to cache get lookups on its own), you could avoid the queries altogether at the cost of memory by adding each new Person and Card object to a temporary dict by ID, and retrieving the existing objects there instead of hitting the database.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文