与 NoSQL 数据库的多对多关系

Posted

技术标签:

【中文标题】与 NoSQL 数据库的多对多关系【英文标题】:Many-to-many relationship with NoSQL database 【发布时间】:2012-12-26 16:44:36 【问题描述】:

我想使用 NoSQL 数据库为我的 node.js 应用程序实现分类结构(地理术语)。我有一个与 mysql 相似的分类结构,但现在是前进并学习新东西的时候了,所以我决定尝试不同的方法并为我的测试应用程序使用 NoSQL(面向文档)。分类结构很简单 - 有五个不同的级别:国家(ie 英国)→ 地区(英格兰)→ 县(默西塞德郡)→ 城市/城镇/村庄(利物浦)→ 城市的一部分(托克斯泰特)。

显而易见的选择是使用树形结构,但细节是魔鬼 - 从历史上看,一些城镇属于其他县。这个想法是用这些术语标记出生在某些城市或城镇的人,然后用地理标签过滤他们,所以我必须尊重利物浦或曼彻斯特(以及其他)在某些人出生时是兰开夏郡的一部分这一事实.否则,任何用户使用我的地理过滤器获得的结果都是不正确的。

示例:John Doe 早在 1957 年就出生在布莱克本(兰开夏郡)。Paul Brown 于 1960 年出生在利物浦(兰开夏郡,现在的默西塞德郡)。乔治亚·多伊 (nee Jones) 5 年后出生在威勒尔(柴郡,现默西塞德郡)。他们的儿子林戈于 1982 年出生在利物浦(当时是默西塞德郡)。

约翰生来是兰开斯特人,保罗是兰开斯特人和默西塞德人,乔治亚同时来自柴郡和默西塞德,林戈来自默西塞德。所以当我按县搜索时,它们应该相应地分类。但是,由于遵循国家现代结构的简单一对多结构,它们永远不会被过滤掉。

如何使用 NoSQL(首先是面向文档的)解决方案来实现集合的结构复杂性?我用谷歌搜索了它并对 stack* 进行了一些研究,但仍然不知道下一步该做什么。我认为有几种可能的方法来解决它:

    使用类似 SQL 的数据结构:

    
        'name': 'United Kingdom', 'unique_id': 1,
        'name': 'England', 'unique_id': 2, 'parents': [1],
        'name': 'Merseyside', 'unique_id': 3, 'parents': [2],
        'name': 'Lancashire', 'unique_id': 4, 'parents': [2],
        'name': 'Liverpool', 'unique_id': 5, 'parents': [3, 4],
    
    

    使用带有一些引用的树结构:

        
        'name': 'United Kingdom', 'unique_id': 1
            'name': 'England', 'unique_id': 2]
                'name': 'Merseyside', 'unique_id': 3]
                    'name': 'Liverpool', 'unique_id': 5, 'alternate_parents': [4],
                ,
                'name': 'Lancashire', 'unique_id': 4,
            ,
        ,
    
    

    使用没有引用的树结构(一对多)并手动将“备用父”标签添加到文档中:

        
        'name': 'United Kingdom', 'unique_id': 1
            'name': 'England', 'unique_id': 2]
                'name': 'Merseyside', 'unique_id': 3]
                    'name': 'Liverpool', 'unique_id': 5,
                ,
                'name': 'Lancashire', 'unique_id': 4,
            ,
        ,
    
    

    坚持使用 SQL。

    尝试实施无数据库分类法。

请给我关于这件事的建议。我是任何 NoSQL 的新手(目前我没有设计过这样的数据库),所以对我来说有一个真正的设计问题。

而且我是 stack* 的新手,所以如果我在这篇文章中做错了什么,请随时纠正我 :) 谢谢!

编辑 我选择@Jonathan 回答作为解决方案。我认为它更适合我的需求(将有其他文档存储在我的数据库中并用这些术语标记它们),尤其是 @Valentyn 建议的 mapReduce 功能。

但是,如果您的应用不需要文档集合,@Philipp 建议的图形数据库(基于关系而不是文档)可能是最好的解决方案。

【问题讨论】:

这解释了 ruby​​ ORM Mongoid 是如何做到的:“在定义这种性质的关系时,每个文档都存储在其各自的集合中,每个文档都包含一个“外键”以数组的形式引用另一个。” mongoid.org/en/mongoid/docs/… 您使用的是什么 NoSQL 数据库?有很多数据库解决方案都归于通用术语“NoSQL”,它们没有太多共同点。 @AlexWayne 谢谢伙计。看起来很有希望,我稍后会试一试。 @Philipp 是的。你是对的伙伴,我应该更具体的条款。会有一个面向文档的数据库,很可能是 MongoDB 实例。 【参考方案1】:

由于您发表的评论,当您说“NoSQL”时,我假设您的意思是“MongoDB”。还有很多其他通常称为 NoSQL 的数据库技术完全不同,但是这似乎是您的意思。

    不是一个好主意,因为要获得整个分类链,您需要执行多个数据库查询,这通常应该避免。

    和 3. 单个文档是一棵巨大的树也不是一个好主意,因为 MongoDB 的每个文档限制为 16MB。当您创建庞大的单一文档时,您可能会达到这个限制。

我认为 MongoDB 可能不是您的用例的最佳解决方案。您是否考虑过使用graph database? MongoDB 针对独立存在的自包含文档进行了优化。但是图形数据库的重点是数据集,其中有很多实体,这些实体由它们与其他实体的关系定义。这看起来很像您的用例。

【讨论】:

谢谢@Philipp。抱歉回复晚了。我认为您的解决方案真的很有趣我从来没有机会尝试图形数据库(我什至不知道它们)所以它可能值得一试,但我不知道它是否真的适合我。我需要用这些术语标记人(和地点),最好将他们的个人资料存储在集合而不是图表中(我不需要存储人和地点的任何关系)。我不知道是否可以在一个应用程序中结合这两种方法(面向文档的数据库和图形数据库),但 IMO 无论如何这对我的应用程序来说都是不必要的开销。【参考方案2】:

首先,如果您不熟悉基本原理,则很难在 NoSQL 和 SQL 数据库之间进行选择。如果这是您要存储的唯一数据,请使用关系 (SQL)。如果有更多数据(我假设)并且需要更多交织模式,请坚持使用 NoSQL。

我会在这方面采取关系路线,以防止它变得过于复杂......开始几个集合;一个用于国家、地区等。不要因为在 NoSQL 数据库中使用关系 (SQL) 类型的模式而气馁;大多数时候,它们是最好的解决方案。

然后,在每个子组中,都有一个命名父组的字段。

例如:


    'name': 'United Kingdom',
    'name': 'United States'



    'name': 'England', 'parent': 'United Kingdom',
    'name': 'California', 'parent': 'United States'

这样,您的数据集不会变得如此嵌套,以至于返回的数据无法管理。然后就可以轻松抓取国家和对应地区...等。

祝你好运!

编辑:回答 OP 的问题:

(首先,我推荐 MongoDB - 这是一个很好的解决方案。)

    因为当您开始使用 MongoDB 时,您会意识到它将数据并排存储在硬盘上。如果你编辑这样一个巨大的记录,它很可能会被推到磁盘的后面,使你的硬盘驱动器类似于瑞士奶酪。一旦达到这一点,您将不得不进行修复以再次凝聚它。此外,这样数据更容易在您的应用程序中分离,这样,如果您需要对数据做一些事情,您不必将其应用于整个对象。我假设您将拥有一个大型数据集,因为世界上有许多不同的位置。

    不要太担心这种事情。如果您打算大量更改名称,您可以使用父母的 ID 并将孩子与 ID 匹配。我之所以这样做,是因为我认为您不需要更改位置数据库。

    我会使用嵌套文档来存储多个父对象,而不是数组。这样,它可以更容易地被查询和索引。我会使用以下方法:

    
        
            'name': 'England,
            'parent': 
                1: 1,
                568: 1
            
         
     
    

这样你就可以运用你的索引概念并找到db.region.$.568 = 1 的位置

【讨论】:

另外,我想在 NoSQL sultuions 中添加这一点,而不是使用 SELECT ... WHERE x IN SELECT .. GROUP BY 使用“Map-Reduce”方法进行查询。例如,要获取“英国”中的所有项目,您可以使用 Map 标记所有需要父项的项目,然后通过过滤标记的项目来减少结果集。所以我会 +1 @Jonathan 解决方案 - 这样你会得到更少的耦合实体,而且实体也会有更多的意义 - 你可以在不发出额外查询的情况下获得必要的数据。 取决于 OP 使用的数据库(不幸的是他没有告诉)这可能需要孩子和父母之间的大量 JOIN 操作。一些 NoSQL 数据库不支持或不支持 JOIN,因此这对它们来说是一个糟糕的解决方案。 感谢@Jonathan 的回复。肯定有更多的数据要存储(以及许多不同类型的文档),所以我有一个明显的选择来尝试 MongoDB 或它的表亲之一……这是一个有趣的解决方案,但作为一个新手,我对它的设计有几个问题: 1.为什么您为相似的数据选择不同的集合,而不是为所有这些数据选择一个集合?有五个粒度级别,因此将有五个具有相同结构的集合 - 每个级别一个。 2. 在您的示例中,父项的名称是一个字符串,因此当我重命名父项(即英国到大不列颠)时,我应该找到它的所有子项并在那里重命名父项的名称。我更喜欢引用简单的名称。 3.如何在此设置中存储多个父母?我应该为它使用数组还是其他任何东西? 谢谢@ValentynShybanov。这非常有用。

以上是关于与 NoSQL 数据库的多对多关系的主要内容,如果未能解决你的问题,请参考以下文章

在 TypeORM 与 GraphQL 的多对多关系上使用数据加载器,查询多对多

数据库优先方法中与联结表的多对多关系

核心数据:与状态的多对多关系

与不同数据类型的多对多关系

Eloquent 中与分类学的多对多关系

与Hibernate的多对多关系的循环序列化