您已经发现为什么“双重用途外键”是antipattern。
您还没有完全指出与此相关的问题;无法使用外键约束强制数据处于有效状态。您想确保在 UserTypeMapper 中的每一行都有一个东西,但“某物”不是任何一张表。正式地你想要一个功能依赖于
user_type_mapper → (system_admin× 1) ∪ (user× 0)
但大多数 sql 数据库不允许您编写外键约束来表达这一点。
看起来很复杂,因为它很复杂。
相反,让我们考虑一下我们真正想说什么; "每个system_admin 都应该是user;或者
system_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' 的 SystemAdmin”?
>>> print session.query(User).join(SystemAdmin).filter(User.name == 'john doe').exists()
EXISTS (SELECT 1
FROM "user" JOIN system_admin ON "user".id = system_admin.user_id
WHERE "user".name = :name_1)
>>> print session.query(func.count(User.id), func.count(SystemAdmin.user_id)).outerjoin(SystemAdmin)
SELECT count("user".id) AS count_1, count(system_admin.user_id) AS count_2
FROM "user" LEFT OUTER JOIN system_admin ON "user".id = system_admin.user_id
我希望您能明白为什么上述设计比您在问题中描述的设计更可取;但是如果您别无选择(只有在这种情况下,如果您仍然觉得自己拥有的更好,请完善您的问题),您仍然可以填充该数据通过提供到表的替代映射,将其转换为单个 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 类中,但我肯定不会这样做。使用非表映射,特别是当它们不仅仅是简单的连接(这是一个联合)时,零回报需要做很多工作。