Python SQLAlchemy映射联接表';列到一个对象

Python SQLAlchemy映射联接表';列到一个对象,python,orm,sqlalchemy,pyramid,Python,Orm,Sqlalchemy,Pyramid,我有三个表:UserTypeMapper、User和SystemAdmin。在我的get_user方法中,根据UserTypeMapper.is_admin行,我然后查询user或SystemAdmin表。user\u id行与user和SystemAdmin表中的主键id相关 class UserTypeMapper(Base): __tablename__ = 'user_type_mapper' id = Column(BigInteger, primary_key=Tr

我有三个表:UserTypeMapper、User和SystemAdmin。在我的
get_user
方法中,根据UserTypeMapper.is_admin行,我然后查询user或SystemAdmin表。
user\u id
行与user和SystemAdmin表中的主键
id
相关

class UserTypeMapper(Base):
    __tablename__ = 'user_type_mapper'

    id = Column(BigInteger, primary_key=True)
    is_admin = Column(Boolean, default=False)
    user_id = Column(BigInteger, nullable=False)

class SystemAdmin(Base):
    __tablename__ = 'system_admin'

    id = Column(BigInteger, primary_key=True)
    name = Column(Unicode)
    email = Column(Unicode)

class User(Base):
    __tablename__ = 'user'

    id = Column(BigInteger, primary_key=True)
    name = Column(Unicode)
    email = Column(Unicode)
我希望能够从一个查询中获得任何用户–系统管理员或普通用户,因此我在
user
SystemAdmin
上进行连接,具体取决于
is\u admin
行。例如:

DBSession.query(UserTypeMapper, SystemAdmin).join(SystemAdmin, UserTypeMapper.user_id==SystemAdmin.id).first()

这很好用;但是,我希望能够访问这些,就像这样:

>>> my_admin_obj.is_admin
True
>>> my_admin_obj.name
Bob Smith

>>> my_user_obj.is_admin
False
>>> my_user_obj.name
Bob Stevens
目前,我必须指定:
my\u user\u obj.UserTypeMapper.is\u admin
my\u user\u obj.user.name
。根据我所阅读的内容,我需要映射表,这样就不需要指定属性属于哪个表。我的问题是,我不明白如何指定它,因为我有两个潜在的表,例如
name
属性可能来自这两个表

这就是我所指的例子:

我怎样才能做到这一点?谢谢。

您已经了解了为什么“双用途外键”是一个

有一个与此相关的问题,你还没有完全指出;无法使用外键约束强制数据处于有效状态。您希望确保UserTypeMapper中的每一行都有一个something,但“something”不是任何一个表。从形式上讲,您希望功能依赖于

用户类型映射器
→(
系统管理
×1)∪ (
用户
×0)

但是大多数sql数据库不允许您编写一个外键约束来表达这一点

它看起来很复杂,因为它很复杂

让我们想想我们真正想说的话;“每个
系统管理员
都应该是
用户
;或者

system\u admin
→
user

在sql中,可以这样写:

CREATE TABLE user (
    id INTEGER PRIMARY KEY,
    name VARCHAR,
    email VARCHAR
);

CREATE TABLE system_admin (
    user_id INTEGER PRIMARY KEY REFERENCES user(id)
);
或者,用sqlalchemy声明式

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

class SystemAdmin(Base):
    __tablename__ = 'system_admin'
    user_id = Column(ForeignKey(User.id), primary_key=True)
这个模式允许我们问什么样的问题

  • “是否有名为‘john doe’的系统管理员?”
  • “有多少用户?有多少系统管理员?”

我希望你能明白为什么上面的设计比你在问题中描述的更可取;但很可能你没有选择(只有在这种情况下,如果你仍然觉得你拥有的更好,请改进你的问题),您仍然可以将该数据塞进一个python对象中,这将非常难以使用,方法是提供一个到表的替代映射;特别是遵循第一个等式中的粗略结构的映射

我们需要两次提到UserTypeMapper,一次用于联盟的每一方,为此,我们需要给出别名

>>> from sqlalchemy.orm import aliased
>>> utm1 = aliased(UserTypeMapper)
>>> utm2 = aliased(UserTypeMapper)
对于联合体,将每个别名连接到相应的表中:由于
SystemAdmin
User
具有相同的列,因此我们不需要详细描述它们,但如果它们完全不同,我们需要通过明确提及每一列使它们“联合兼容”;这只是一个练习

>>> utm_sa = Query([utm1, SystemAdmin]).join(SystemAdmin, (utm1.user_id == SystemAdmin.id) & (utm1.is_admin == True))
>>> utm_u = Query([utm2, User]).join(User, (utm2.user_id == User.id) & (utm2.is_admin == False))
然后我们把他们连在一起

>>> print utm_sa.union(utm_u)
SELECT anon_1.user_type_mapper_1_id AS anon_1_user_type_mapper_1_id, anon_1.user_type_mapper_1_is_admin AS anon_1_user_type_mapper_1_is_admin, anon_1.user_type_mapper_1_user_id AS anon_1_user_type_mapper_1_user_id, anon_1.system_admin_id AS anon_1_system_admin_id, anon_1.system_admin_name AS anon_1_system_admin_name, anon_1.system_admin_email AS anon_1_system_admin_email 
FROM (SELECT user_type_mapper_1.id AS user_type_mapper_1_id, user_type_mapper_1.is_admin AS user_type_mapper_1_is_admin, user_type_mapper_1.user_id AS user_type_mapper_1_user_id, system_admin.id AS system_admin_id, system_admin.name AS system_admin_name, system_admin.email AS system_admin_email 
FROM user_type_mapper AS user_type_mapper_1 JOIN system_admin ON user_type_mapper_1.user_id = system_admin.id AND user_type_mapper_1.is_admin = 1 UNION SELECT user_type_mapper_2.id AS user_type_mapper_2_id, user_type_mapper_2.is_admin AS user_type_mapper_2_is_admin, user_type_mapper_2.user_id AS user_type_mapper_2_user_id, "user".id AS user_id, "user".name AS user_name, "user".email AS user_email 
FROM user_type_mapper AS user_type_mapper_2 JOIN "user" ON user_type_mapper_2.user_id = "user".id AND user_type_mapper_2.is_admin = 0) AS anon_1

虽然理论上可以将这一切封装到一个看起来有点像标准sqlalchemy orm的python类中,但我肯定不会这样做,需要做大量的工作才能实现零回报。

两点:1.我刚开始与星展银行合作,我正在购买您链接的那本书。我对它的外观非常满意。2.这是一个非常好的答案,我将接受您最初的建议。它更有意义,而且更易于使用。非常感谢!(注意:我会在19小时后奖励奖金。)@JohnZ:即使是有一些数据库经验的人也会提出这种设计;一些流行的ORM鼓励这种设计并生成这种精确的模式(如ActiveRecord)也无济于事。
>>> from sqlalchemy.orm import aliased
>>> utm1 = aliased(UserTypeMapper)
>>> utm2 = aliased(UserTypeMapper)
>>> utm_sa = Query([utm1, SystemAdmin]).join(SystemAdmin, (utm1.user_id == SystemAdmin.id) & (utm1.is_admin == True))
>>> utm_u = Query([utm2, User]).join(User, (utm2.user_id == User.id) & (utm2.is_admin == False))
>>> print utm_sa.union(utm_u)
SELECT anon_1.user_type_mapper_1_id AS anon_1_user_type_mapper_1_id, anon_1.user_type_mapper_1_is_admin AS anon_1_user_type_mapper_1_is_admin, anon_1.user_type_mapper_1_user_id AS anon_1_user_type_mapper_1_user_id, anon_1.system_admin_id AS anon_1_system_admin_id, anon_1.system_admin_name AS anon_1_system_admin_name, anon_1.system_admin_email AS anon_1_system_admin_email 
FROM (SELECT user_type_mapper_1.id AS user_type_mapper_1_id, user_type_mapper_1.is_admin AS user_type_mapper_1_is_admin, user_type_mapper_1.user_id AS user_type_mapper_1_user_id, system_admin.id AS system_admin_id, system_admin.name AS system_admin_name, system_admin.email AS system_admin_email 
FROM user_type_mapper AS user_type_mapper_1 JOIN system_admin ON user_type_mapper_1.user_id = system_admin.id AND user_type_mapper_1.is_admin = 1 UNION SELECT user_type_mapper_2.id AS user_type_mapper_2_id, user_type_mapper_2.is_admin AS user_type_mapper_2_is_admin, user_type_mapper_2.user_id AS user_type_mapper_2_user_id, "user".id AS user_id, "user".name AS user_name, "user".email AS user_email 
FROM user_type_mapper AS user_type_mapper_2 JOIN "user" ON user_type_mapper_2.user_id = "user".id AND user_type_mapper_2.is_admin = 0) AS anon_1