【问题标题】:SQL field with an foreign id from two tables来自两个表的具有外部 id 的 SQL 字段
【发布时间】:2017-07-12 15:41:28
【问题描述】:

我正在使用 sqlalchemy 构建一个 postgressql 数据库。我想知道是否有可能有这样的结构:

由人员和组织组成的两张表。

Table - Person - 
person_id, name
1,         Jeffery
2,         Frank

Table - Org -
org_id, name
a,      Pepsi
b,      Microsoft

第三张地址表或个人或组织:

Table - Addresses -
address_id, type_of_id, (either person_id or org_id), addresses
1,          person,     1, "2 Jeffery way"
2,          person,     1, "7 Holland Avenue"
3,          org,        b, "1 Microsoft way"
4,          person,     2, "2 Frank Street"

postgres 可以做到这一点吗?以及如何将其写入 sqlalchemy python 代码?

更新:

我相信这被称为多态关联,如下所示: Possible to do a MySQL foreign key to one of two possible tables?

【问题讨论】:

  • 这实际上是可能的,尽管从关系的角度来看,没有多大意义。您通常会让表 PersonOrg 有一个列 address_id,引用 Addresses(address_id) 并让地址是地址(没有人员组织或人员 ID)。
  • 如果我每个人有多个地址,而组织有多个地址怎么办?
  • 我会选择两个“链接表”:一个用于person_x_address (person_id, address_id),另一个用于organization_x_address (organization_id, address_id)。它将比大多数其他选项更干净

标签: python postgresql sqlalchemy


【解决方案1】:

您的问题由两部分组成:

  1. 创建人员和组织表的联合。
  2. 将实体类型(个人/组织)添加到表中。

第一部分可以使用 union_all 完成。有关这方面的更多信息,请访问:

第二部分,添加实体类型(人/组织)可以使用literal_column来实现。有关这方面的更多信息:

以下是结合这些元素的简短示例:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

Base = declarative_base()


## Define sample models


class Person(Base):
    __tablename__ = 'person'
    person_id = Column('id', Integer, primary_key=True)
    name = Column('name', String(250))

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

    def __repr__(self):
        return '[{person_id}] - {name}'.format(person_id=self.person_id, name=self.name)


class Orqanisation(Base):
    __tablename__ = 'organisation'
    organisation_id = Column('id', Integer, primary_key=True)
    name = Column('name', String(250))

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

    def __repr__(self):
        return '[{organisation_id}] - {name}'.format(organisation_id=self.organisation_id, name=self.name)


engine = create_engine('sqlite:///')
session = sessionmaker()
session.configure(bind=engine)
ex_ses = session()
Base.metadata.create_all(engine)

## Create sample data

# http://listofrandomnames.com/ for something else then A,B,...
names = ['Virgil', 'Ina', 'Oleta', 'Suzette', 'Usha', 'Ilda', 'Lorean', 'Cinthia', 'Sheba', 'Waneta', 'Donnette']

organisations = ['stackoverflow','Cross validated','Meta stack overflow','Area 51']

# Create persons
for name in names:
    ex_ses.add(Person(name=name))

# Create organisations
for org in organisations:
    ex_ses.add(Orqanisation(name=org))

ex_ses.commit()

# queries
print('Persons:')
for person in ex_ses.query(Person).all():
    print('* ',person)

'''
Persons:
*  [1] - Virgil
*  [2] - Ina
*  [3] - Oleta
*  [4] - Suzette
*  [5] - Usha
*  [6] - Ilda
*  [7] - Lorean
*  [8] - Cinthia
*  [9] - Sheba
*  [10] - Waneta
*  [11] - Donnette
'''

print('Organisations:')
for org in ex_ses.query(Orqanisation).all():
    print('* ',org)

'''
Organisations:
*  [1] - stackoverflow
*  [2] - Cross validated
*  [3] - Meta stack overflow
*  [4] - Area 51
'''

# Query combining tables, adding type of entity

from sqlalchemy.sql.expression import literal_column

persons = ex_ses.query(Person.person_id, Person.name,literal_column("'person'").label('type'))
organisations = ex_ses.query(Orqanisation.organisation_id, Orqanisation.name,literal_column("'Organisation'").label('type'))

print('Persons - Organisations:')
for pers_org in persons.union_all(organisations).all():
    print('* {id} - {name} (type: {type})'.format(id=pers_org[0],name=pers_org[1],type=pers_org[2]))

'''
Persons - Organisations:
* 1 - Virgil (type: person)
* 2 - Ina (type: person)
* 3 - Oleta (type: person)
* 4 - Suzette (type: person)
* 5 - Usha (type: person)
* 6 - Ilda (type: person)
* 7 - Lorean (type: person)
* 8 - Cinthia (type: person)
* 9 - Sheba (type: person)
* 10 - Waneta (type: person)
* 11 - Donnette (type: person)
* 1 - stackoverflow (type: Organisation)
* 2 - Cross validated (type: Organisation)
* 3 - Meta stack overflow (type: Organisation)
* 4 - Area 51 (type: Organisation)
'''

【讨论】:

    【解决方案2】:

    我看到了在关系数据库中对此建模的两种方法:

    1. 关系表

      CREATE TABLE address_assignment (
         person_id integer REFERENCES person,
         org_id text REFERENCES org,
         address_id integer NOT NULL REFERENCES address,
         CHECK (person_id IS     NULL AND org_id IS NOT NULL
             OR person_id IS NOT NULL AND org_id IS     NULL),
         UNIQUE (person_id, org_id, address_id)
      );
      

      如果不同的实体可以共享一个地址,此模型将特别有用。

    2. “穷人的超类”

      为个人和组织创建一个实现超类的表,可能包含所有常见属性:

      CREATE TABLE located_entity (
         locent_id INTEGER PRIMARY KEY,
         name text NOT NULL
      );
      

      personorg 都获得了located_entity 的外键,您可以通过将located_entity 的外键添加到address 来实现与address 的关系。

      如果您通常按地址而不是按人的地址来查找人员,则可以考虑在 located_entitypersonorg 之间添加外键,以另一个方向使用类似的检查约束,如解决方案 1.

    【讨论】:

    • 我实现了上面的选项 2
    【解决方案3】:

    改编自:http://www.duanqu.tech/questions/2898814/sqlalchemy-polymorphic-associationhttp://docs.sqlalchemy.org/en/latest/orm/inheritance.html

    from sqlalchemy.ext.declarative import as_declarative, declared_attr
    from sqlalchemy import create_engine, Integer, Column, \
        String, ForeignKey
    from sqlalchemy.orm import Session, relationship
    from sqlalchemy_utils import database_exists, create_database
    from sqlalchemy.dialects.postgresql import UUID
    import uuid
    
    
    @as_declarative()
    class Base(object):
        # Set the tablenames
        # to the class names
        @declared_attr
        def __tablename__(cls):
            return cls.__name__.lower()
        # give an id to each class
        id = Column(UUID, default=lambda: str(uuid.uuid4()), primary_key=True)
    # Base = declarative_base()
    
    
    class EntityInterface(Base):
        discriminator = Column(String)
    
        __mapper_args__ = {"polymorphic_on": discriminator}
    
    
    class Address(Base):
        street = Column(String)
        city = Column(String)
        zip = Column(String)
        entity_id = Column(UUID, ForeignKey(EntityInterface.id),
                           default=lambda: str(uuid.uuid4()))
        entity = relationship(EntityInterface)
    
        def __repr__(self):
            return ("%s(street=%r, city=%r, zip=%r, company=%r)" %
                    (self.__class__.__name__, self.street, self.city,
                     self.zip, self.entity))
    
    
    class Person(EntityInterface):
        id = Column(UUID, ForeignKey(EntityInterface.id),
                    default=lambda: str(uuid.uuid4()), primary_key=True)
        name = Column(String)
        __mapper_args__ = {"polymorphic_identity": "Person"}
    
    
    class Organization(EntityInterface):
        id = Column(UUID, ForeignKey(EntityInterface.id),
                    default=lambda: str(uuid.uuid4()), primary_key=True)
        name = Column(String)
        __mapper_args__ = {"polymorphic_identity": "Organization"}
    
    
    engine = create_engine(
        'postgresql://paul_tsc:paul123@localhost/' + "poly_testing",
        echo=False)
    
    # if it doesn't exist, create it
    if not database_exists(engine.url):
        create_database(engine.url)
    
    Base.metadata.create_all(engine)
    session = Session(engine)
    
    address1 = Address(street='test-1', city="Detroit", zip="56785")
    address2 = Address(street='test-2', city="Phoenix", zip="110322")
    address3 = Address(street='test-3', city="Washington", zip="432414")
    
    org1 = Organization(name="Org-1 TEST")
    org2 = Organization(name="Org-2 TEST")
    person1 = Person(name="Person-1 TEST")
    person2 = Person(name="Person-2 TEST")
    
    address1.entity = org1
    address2.entity = person1
    address3.entity = person1
    
    session.add_all([address1, address2, address3])
    
    session.commit()
    
    address3.entity = org2
    
    session.commit()
    
    print("PRINTING, TOTAL = %s" % session.query(Address).count())
    for address in session.query(Address):
        print("ADDRESS = %s" % address)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-25
      • 1970-01-01
      • 2015-03-14
      • 2012-12-07
      • 1970-01-01
      • 2015-12-13
      • 1970-01-01
      相关资源
      最近更新 更多