带有两个表的外部id的SQL字段

时间:2017-07-12 03:00:49

标签: python postgresql sqlalchemy

我正在用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?

3 个答案:

答案 0 :(得分:1)

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

  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的外键,您可以通过向address添加外键来实现与located_entity的关系到address

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

答案 1 :(得分:0)

您的问题由两部分组成:

  1. 创建人员和组织表的联合。
  2. 将实体类型(人/组织)添加到表格中。
  3. 第一部分可以使用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 :(得分:0)

改编自: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)