SQLAlchemy插入多对一条目

时间:2013-08-02 16:56:43

标签: sqlalchemy many-to-one

很抱歉,如果这是一个新手问题,但有关many-to-one relationship的文档似乎并未涉及此问题。我一直在寻找类似于this的东西(在“如何插入/向表中添加数据”部分下),但是在所示的示例中,这始终是唯一的插入。

基本上,我想用我本地计算机上的数据填充数据库。为了简单起见,我将下面显示的示例构建到一个 MWE 中,以说明问题。该问题由两个名为PriceCurrency的表组成,实现以声明方式完成。

model.py

from sqlalchemy import Column, Integer, String
from sqlalchemy import Float, BigInteger, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Currency(Base):
    __tablename__ = 'Currency'

    id = Column(Integer, primary_key=True)
    unit = Column(String(16), unique=True)

    def __init__(self, unit):
        self.unit = unit

class Price(Base):
    __tablename__ = 'Price'

    id = Column(BigInteger, primary_key=True)

    currency_id = Column(Integer, ForeignKey("Currency.id"), nullable=False)
    currency = relationship("Currency", backref="Currency.id")

    hour1 = Column(Float)
    hour2 = Column(Float)

    def __init__(self, hour1, hour2):
        self.hour1 = hour1
        self.hour2 = hour2

目前,我使用以下代码填充数据库:

script.py

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

from model import *

engine = create_engine('sqlite:///example.db', echo=True)

db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
session = db_session()

Base.metadata.create_all(engine)

oPrice = Price(2.5, 2.5)
oPrice.currency = Currency("EUR")
session.add(oPrice)

tPrice = Price(5.5, 1.5)
tPrice.currency = Currency("EUR")
session.add(tPrice)

session.commit()

这会产生错误

sqlalchemy.exc.IntegrityError: (IntegrityError) column unit is not unique u'INSERT INTO "Currency" (unit) VALUES (?)' ('EUR',)

填充数据库的最佳策略是什么,以确保我的Currency.id和Price.currency_id映射是正确的?我应该让模型类在初始化之前寻找唯一性吗?我是否与其他表关联?

2 个答案:

答案 0 :(得分:2)

最简单的解决方案是使用货币代码作为货币的主键,使用Price中的外键。那么你可以拥有

price.currency_id = "EUR"

这也使你的数据库表更具可读性 - 就像你不会有28342而是'GBP'。

答案 1 :(得分:2)

我认为Antti建议的是因为货币有标准代码,如'INR','USD'等,你可以将currency_code作为主键。

或者如果您想保留数字主键,则其中一个选项是: http://www.sqlalchemy.org/trac/wiki/UsageRecipes/UniqueObject

编辑(根据上面链接中的配方添加示例,带有decoartor类的配方)

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

engine = create_engine('sqlite:///example.db', echo=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                     autoflush=False,
                                     bind=engine))

model.py

from sqlalchemy import Column, Integer, String
from sqlalchemy import Float, BigInteger, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declarative_base

from database import db_session

Base = declarative_base()

def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw):
    cache = getattr(session, '_unique_cache', None)
    if cache is None:
        session._unique_cache = cache = {}

    key = (cls, hashfunc(*arg, **kw))
    if key in cache:
        return cache[key]
    else:
        with session.no_autoflush:
            q = session.query(cls)
            q = queryfunc(q, *arg, **kw)
            obj = q.first()
            if not obj:
                obj = constructor(*arg, **kw)
                session.add(obj)
        cache[key] = obj
        return obj

def unique_constructor(scoped_session, hashfunc, queryfunc):
    def decorate(cls):
        def _null_init(self, *arg, **kw):
            pass
        def __new__(cls, bases, *arg, **kw):
            # no-op __new__(), called
            # by the loading procedure
            if not arg and not kw:
                return object.__new__(cls)

            session = scoped_session()

            def constructor(*arg, **kw):
                obj = object.__new__(cls)
                obj._init(*arg, **kw)
                return obj

            return _unique(
                    session,
                    cls,
                    hashfunc,
                    queryfunc,
                    constructor,
                    arg, kw
               )

      # note: cls must be already mapped for this part to work
        cls._init = cls.__init__
        cls.__init__ = _null_init
        cls.__new__ = classmethod(__new__)
        return cls

    return decorate




@unique_constructor(
db_session,
lambda unit: unit,
lambda query, unit: query.filter(Currency.unit == unit)
)
class Currency(Base):
    __tablename__ = 'Currency'

    id = Column(Integer, primary_key=True)
    unit = Column(String(16), unique=True)

    def __init__(self, unit):
        self.unit = unit

class Price(Base):
    __tablename__ = 'Price'

    id = Column(BigInteger, primary_key=True)

    currency_id = Column(Integer, ForeignKey("Currency.id"), nullable=False)
    currency = relationship("Currency", backref="Currency.id")

    hour1 = Column(Float)
    hour2 = Column(Float)

    def __init__(self, hour1, hour2):
        self.hour1 = hour1
        self.hour2 = hour2

script.py:

from model import *
from database import engine, db_session as session

Base.metadata.create_all(engine)

oPrice = Price(2.5, 2.5)
oPrice.currency = Currency("EUR")
session.add(oPrice)

tPrice = Price(5.5, 1.5)
tPrice.currency = Currency("EUR")
session.add(tPrice)

session.commit()