【问题标题】:How do I relate data without one single master or starting point如何在没有单一主数据或起点的情况下关联数据
【发布时间】:2013-09-24 08:46:54
【问题描述】:

我有一组包含身份证号码的“人”数据。这些数据来自各种来源,格式如下

Source1: IDNumber:I1, Passport:P1,SocialSecurity:S1,DateOfBirth,13/03/1967
Source2: Passport:P1,VATNumber:V1,marital_status,Married
Source3: TaxNumber:T1,IDNumber:I1,HasPaidTax,True

假设同一行中提供的数字是相关的。因此,从上面的集合中,我们可以做出以下假设: I1 与 P1、S1、V1、T1 相关,这意味着所有这些身份都属于一个人,因此在三个实例中提供的数据,即 DateOfBirth、MaritalStatus、HasPaidTax 都属于一个人。

目前,所有这些不同的 IDType 都放在一个表中:

PID=======IDTYpe=======IDNumber
 1---------IDNumber-----I1
 2---------Passport-----P1
 3---------VATNumber----V1
 etc

问题是,我如何将这个 ID 号码的相关性质存储在数据库中?在我的搜索中,我遇到了adjacency list model and nested set models。然而,这是用于存储分层信息。就我而言,没有什么是真正的父母或孩子。它不是家谱。它只是水平相互关联的数字。没有一种 ID 类型是主 ID

我使用 python、postgresql 和 SQLAlchemy 作为 ORM,它具有一些 nested 功能,但我仍然不确定我在这里所拥有的是否可以分层表示......

【问题讨论】:

  • 具有一个(人工序列)主键然后指向其他表(Source1、Source2、Source3)的表呢?例如表:id_party (PK), IDNumber (FK), Passport (FK), VATNumber (FK)?那么IDNumberSource1中的主键,PassportSource2中的主键等等。
  • 人工序列是指数据库中主键的自动增量?也就是说,没有source_tables。数据源是 XML 和/或平面文件。当所有数据都进来时,我将身份号码与 IDType 和 IDNumber 放在一个表格中,如上所示。理想情况下,我希望它保留在一张桌子上,也许另一张桌子只是显示关系。希望我已经回答了其他请详细说明您的建议

标签: python postgresql database-design sqlalchemy relationship


【解决方案1】:

我想我终于找到了适合我的问题的解决方案...我将在这里展示如何以两种方式存储关系。在关系数据库中使用嵌套集模型并使用具有持久性的基于键值的解决方案

解决方案 1:拉你的头发代码:嵌套集模型

CREATE TABLE identity
(
  id serial NOT NULL,
  identity_type_id integer NOT NULL,
  "number" character varying(50) NOT NULL,
  CONSTRAINT identity_pkey PRIMARY KEY (id),
  CONSTRAINT identity_identity_type_id_fkey FOREIGN KEY (identity_type_id)
      REFERENCES config_identity_type (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT identity_1 UNIQUE (identity_type_id, number)
)

CREATE TABLE identity_related
(
  id serial NOT NULL,
  identity_id integer NOT NULL,
  is_processed boolean NOT NULL DEFAULT false,
  ref_no character varying(20) NOT NULL,
  lft integer,
  rgt integer,
  CONSTRAINT identity_related_pkey PRIMARY KEY (id),
  CONSTRAINT identity_related_identity_id_fkey FOREIGN KEY (identity_id)
      REFERENCES identity (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

对于我通过的每一行,我获取该行上的所有标识号,生成一个唯一的参考号,然后使用嵌套集模型,我将各自的左右值设置到 identity_related 中。它奏效了。

嵌套集合模型指出的唯一挑战是更新集合是惩罚性的。在我的情况下,我需要检查每个 idnumber 是否已保存,获取保存它的 ID,然后,还要检查它们保存的 idnumber,循环一直持续到最后......

一旦完成迭代,我会生成一个新的参考编号并设置所有获取的 ID lft & rgt。查询有效。但是对于大约 100 万条 identity_related 条目,这个查询需要 5 天,但这仅仅是因为我在第 5 天杀死了它,到那时,它已经完成了大约 700,000 个 ID。

代码如下所示:

def relate_identities(self, is_processed):
    #http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
    #http://www.sitepoint.com/hierarchical-data-database-2/
    #http://www.pure-performance.com/2009/03/managing-hierarchical-data-in-sql/
    #http://www.sqlalchemy.org/trac/browser/examples/nested_sets/nested_sets.py
    identify = Identity()
    session = Session()
    entries = []
    related = []
    tbl = IdentityRelated
    not_processed = False
    log_counter = 0
    id_counter = 0
    while True:
        #Get the initial record
        identity = session.query(tbl).filter(tbl.is_processed == is_processed).order_by(tbl.id).first()
        entries.append({identity:'not_processed'})
        related.append(identity)
        if len(entries) == 0: break
        #for key, value in entries[0].items():
            #print("ID:%s; ref_no:%s" %(key.id, key.ref_no))
        while True:
            for entry in entries:
                if not_processed == True: break
                for key, value in entry.items():
                    if value == 'not_processed':
                        not_processed = True
                        break
                    
            if not_processed == False: 
                break
            else:
                not_processed = False
            
            for entry in entries:
                for key, value in entry.items():
                    if value == 'not_processed': 
                        #Get objects which have the same identity_id as current object
                        duplicates = session.query(tbl).filter(tbl.identity_id == key.id).\
                                                            order_by(tbl.id).all()  
                        if len(duplicates) != 0: 
                            for duplicate in duplicates:
                                if not duplicate in related:
                                    related.append(duplicate)
                                    entries.append({duplicate:'not_processed'})
    
                        for entry in entries:
                            for key, value in entry.items():
                                if value == 'not_processed': 
                                    #Get objects that have the same reference numbers as all entries that we have fetched so far
                                    ref_nos = session.query(tbl).filter(tbl.ref_no == key.ref_no).order_by(tbl.id).all()
                                    for ref_no in ref_nos:
                                        if not ref_no in related:
                                            related.append(ref_no)
                                            entries.append({ref_no:'not_processed'})
                                    #Remove current entry from entries
                                    entries.remove(entry)
                                    #Add the entry but change the status
                                    entries.append({key:'processed'})
                    
        #Generate a new RelationCode
        while True:
            ref_no = get_reference_no(REFERENCE_NO.idrelation)
            params = {'key':'relation','relation':ref_no}
            if identify.get_identity(session, **params) == None:
                break             
        #Add each relatedID to the DB and set the Nested Set Value
        #Set is_processed as True to ensure we don't run it again
        relation_counter = 0
        for entry in entries:
            for key, value in entry.items():
                key.ref_no = ref_no
                key.lft = relation_counter + 1
                key.rgt = ((len(related) * 2) - relation_counter)
                key.is_processed = True
                relation_counter += 1  
    
        #Reset values
        log_counter += 1
        id_counter += 1
        related = []
        entries = []
            
        #Commit the session
        session.commit() 

即使此代码经过优化并变得更快,查询相关 ID 也涉及获取我想要的 ID、获取关联的参考号,然后针对该参考号调用 SQL distinct 搜索以获取不同的与该身份相关的身份证号码。

解决方案 2:3 行代码:NoSQL - Redis Key:Value Sets

回到绘图板,即 Google。是否搜索了“存储相关身份号码”是的,我是那么绝望……我得到了一篇文章 Storing hundreds of millions of simple key-value pairs in Redis 的 Instagram 工程。假设Redis 是我最好的新朋友,尤其是因为我花了 10 分钟阅读介绍,5 分钟完成安装,40 分钟完成 3 个基本教程。在那之后,我花了 3 个小时才真正解决了我的问题,而对于 Redis,这基本上意味着试图找出最有效的方式来存储我的身份号码的键:值对。现在我想我已经在 4 行代码中使用 Redis Sets 解决了。

在获取了三个一起提交的身份号码后,我创建了一个名为关系的列表。使用 Redis Sets,你不能有重复的值,所以即使我的三个身份号码被多次提交,我的集合的长度也永远不会增长,并且我不会像上面的关系数据库那样有重复的值。如果添加了额外的第 4 个 ID,那么我的集合会增加 1。重要的是,对于相同数量的身份,此代码耗时 2 小时 23 分钟,总内存消耗为:'used_memory_peak_human':'143.11M'

for related_outer in relation:
    #Create a set using the ID_Number as the key, and the other ID numbers as the values
    for related_inner in relation:
        redis_db.sadd(related_outer.number, related_inner.number)

我最好的新朋友。 Redis....

我欢迎提供信息以改进上述内容或存储关系的全新方式。

【讨论】:

    【解决方案2】:

    创建第二个表Person,只存储主键:

    class Person(Base):
        id = Column(Integer, primary_key=True)
        idtentiy_numbers = relationship("IdentityNumber", backref="person")
    
    class IdentityNumber(Base):
        ...
        person_id = Column(Integer, ForeignKey("person.id")
    

    这里的基本思想是您将存储一个 ID 以作为分组依据。上面的解决方案是一个干净的解决方案,因为它还包含一个可用 ID 的列表。您也可以通过不将person_id 设为外键来在单个表中进行此操作:然后它将仅包含任意数字,例如1, 2, 3, ...

    通过找出person_id 是什么并按它进行分组,您可以通过任何方式找出属于一起的数据。

    【讨论】:

    • 我使用了您的示例并创建了一个嵌套集模型......然后它变得太复杂而无法更新。然后我用 Redis 重写了它。我已经发布了答案。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-15
    • 2017-09-05
    • 1970-01-01
    • 1970-01-01
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多