NoSQL数据库中的多对多关系

22

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

显而易见的选择是使用树形结构,但问题在于细节-历史上有些城市和城镇属于其他县。这样做的目的是为了给出生于某些城市或城镇的人打上标签,并在以后通过地理标签进行过滤,因此我必须尊重利物浦或曼彻斯特(以及其他一些城市)当时是兰开夏郡的一部分,这样任何用户通过我的地理过滤器得到的结果就不会有误。

例如:John Doe于1957年出生于兰开郡的布莱克本。 Paul Brown于1960年在利物浦(兰开夏,现在是默西塞德)出生。Georgia Doe(原名Jones)5年后在威勒(切谢尔,现在是默西塞德)出生。他们的儿子Ringo于1982年在利物浦(那时是默西塞德)出生。John出生于兰开夏,Paul是兰开夏人和默西塞德人,Georgia同时来自切谢尔和默西塞德,Ringo来自默西塞德。因此,当我按县搜索时,他们应该被相应地分类。但是使用现代国家的简单一对多的结构,他们永远不会被正确过滤。

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

  1. 使用类似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]},
    }
    
  2. 使用带有一些引用的树形结构:

    {    
        {'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},
            },
        },
    }
    
  3. 使用树形结构,不要使用参考(一对多),并手动为文档添加“备用父级”标签:

  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},
            },
        },
    }
    
  5. 坚持使用SQL。

  6. 尝试实现无数据库的分类法。

请给我在这个问题上提点建议。我对任何NoSQL都是新手(目前没有设计此类数据库),所以这对我来说确实是一个设计问题。

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

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

但是,如果你的应用程序不需要文档集合,那么@Philipp建议的基于关系而非文档的图形数据库可能是最佳解决方案。


这里解释了Ruby ORM Mongoid的工作原理:“当定义这种关系时,每个文档都存储在其各自的集合中,并且每个文档都包含一个指向另一个文档的“外键”引用,以数组的形式表示。” http://mongoid.org/en/mongoid/docs/relations.html#has_and_belongs_to_many - Alex Wayne
你正在使用哪个NoSQL数据库?有很多数据库解决方案被归为“NoSQL”,它们之间没有太多共同点。 - Philipp
@AlexWayne 谢谢伙计。看起来很有前途,我稍后会试一下。 - Ivan Potapov
@Philipp 是的。你说得对,伙计,我应该更具体地说明这些术语。将会有一个面向文档的数据库,很可能是MongoDB实例。 - Ivan Potapov
2个回答

9
因为你的评论,我认为你在说“NoSQL”时指的是“MongoDB”。还有很多其他常被称为NoSQL的数据库技术,它们完全不同,但这似乎是你所指的。
1. 不是一个好主意,因为要获取整个分类法链,您将需要执行多个数据库查询,通常应避免这样做。 2. 和3. 一个巨大的树形结构单个文档也不是一个好主意,因为MongoDB每个文档的限制为16MB。当您创建巨大的、单块的文档时,可能会达到该限制。
我认为MongoDB可能不是您的用例的最佳解决方案。您考虑过使用图形数据库吗?MongoDB针对独立自成一体的文档进行了优化。但是,图形数据库的重点是数据集,其中有许多实体由其与其他实体的关系来定义。这看起来很像您的用例。

谢谢@Philipp。抱歉回复晚了。我认为你的解决方案非常有趣,我从未尝试过图形数据库(我甚至不知道它们存在),所以值得一试,但我不知道它是否真正适合我。我需要使用这些术语标记人物(和地点),将其存储在集合中而不是图形中可能更好(我不需要为人物和地点存储任何关系)。我不知道是否可以在一个应用程序中结合两种方法(面向文档的数据库和图形数据库),但在我看来,这对我的应用程序来说将是不必要的开销。 - Ivan Potapov

6
首先,如果您不熟悉基本原则,挑选NoSQL和SQL数据库之间可能很困难。如果这是您唯一存储的数据,请选择关系型(SQL)数据库。如果有更多的数据(我认为是这样),并且需要更复杂的架构,则要毫不犹豫地选择NoSQL数据库。
对于这个问题,我会选择关系型(SQL)数据库路线,以避免变得过于复杂...建立几个集合;一个用于国家,一个用于地区等等。不要因在NoSQL数据库中使用关系型(SQL)类型的架构而感到灰心丧气;大多数情况下,它们都是最佳解决方案。
然后,在每个子组中,都有一个字段来命名父类。
例如:
{
    {'name': 'United Kingdom'},
    {'name': 'United States'}
}

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

这样,您的数据集不会嵌套太深,以至于返回的数据难以管理。然后,您可以轻松地获取国家和相应的地区等信息。

祝你好运!

编辑:回答OP的问题:

(首先,我建议使用MongoDB-它是一个非常好的解决方案。)

  1. Because when you start working with MongoDB, you'll realize that it stores data side by side on the hard drive. If you edit a huge record like that, it will most likely be pushed to the back of the disk, making your hard drive similar to Swiss cheese. Once you get to that point, you'll have to do a repair to condense it once more. Also, this way the data is more easily separated in your application, that way, if you need to do something with the data, you won't have to apply it to the entire object. I am assuming that you will have a large dataset since there are many different locations in the world.

  2. Don't worry too much about that kind of thing. You can use ID's for the parent and match the children with the ID if you plan on changing names a lot. I just did it this way because I assumed you wouldn't need to change a location database.

  3. Rather than an array, I would use a nested document to store multiple parents. That way, it can be more easily queried and indexed. I would use the following method:

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

这样,您就可以运用索引的思想,找到 db.region.$.568 = 1 的位置。


2
另外,我想补充一点,在NoSQL解决方案中,不是使用“SELECT ... WHERE x IN”或“SELECT .. GROUP BY”进行查询,而是使用“Map-Reduce”方法。例如,要获取所有在“英国”的项目,您可以使用Map标记所有具有所需父项的项目,然后通过过滤标记的项目来减少结果集。因此,我会+1给@Jonathan的解决方案-这样,您将获得更少的耦合实体,并且实体将具有更多的含义-您可以获取必要的数据而无需发出其他查询。 - Valentyn Shybanov
根据 OP 所使用的数据库(不幸的是他没有告诉我们),这可能需要许多子父级之间的联接操作。一些 NoSQL 数据库对 JOIN 操作没有或支持较差,因此这对它们来说将是一个糟糕的解决方案。 - Philipp
感谢@Jonathan的回复。肯定有更多的数据需要存储(以及许多不同类型的文档),所以对我来说,尝试MongoDB或其类似产品是一个明显的选择...这是一个有趣的解决方案,但作为一个新手,我对它的设计有一些问题: - Ivan Potapov
  1. 为什么选择在不同的集合中存储相似数据,而不是在一个集合中存储所有数据?由于有五个粒度级别,所以将会有五个具有相同结构的集合 - 每个级别都有一个。
  2. 在你的示例中,父级名称是一个字符串,所以当我重命名父级术语(例如,将“英国”改为“大不列颠”),我应该查找所有子级并在那里重命名父级的名称。我更喜欢使用普通名称的引用。
  3. 在这种设置中如何存储多个父级?我应该使用数组还是其他任何方式?
- Ivan Potapov
@IvanPotapov 通过你的问题修改了我的答案。 - Jonathan
对不起,@Jonathan,回复晚了。感谢您的澄清。有趣的是,昨天我注意到这里有一个说明:[link]http://docs.mongodb.org/manual/tutorial/model-tree-structures-with-child-references/ ,上面写着:“这种模式也可以提供存储图形的合适解决方案,其中一个节点可能有多个父节点。”看起来它符合我的需求。我选择了您的答案作为解决方案。 @ValentynShybanov关于mapReduce的重要说明使它变得更好。 - Ivan Potapov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接