SQL和MONGODB查询功能之间的相似之处

308 阅读10分钟

SQL和MONGODB查询功能之间的相似之处

下载例子数据

虽然MongoDB是一个文档数据库,与关系数据库几乎没有相似之处,但MongoDB查询语言感觉很像SQL。

要了解MongoDB查询语言功能并了解其性能,请首先将数据集加载到MongoDB数据库中

我加载了数百万个电影评级记录的MovieLens数据集。

首先,在 以下网址下载包含 100 万条电影评分记录的数据集。

电影镜头|组镜头 (grouplens.org)

下载包有 tar.gz(压缩和压缩)和.zip存档格式。下载最适合您平台的格式。

获取捆绑包后,将存档文件的内容解压缩到文件系统中的文件夹中。

在提取 100 万评级数据集时,您应该有以下三个文件:

movie.dat

ratings.dat

users.dat

电影.dat数据文件包含电影本身的数据。此文件包含 3,952 条记录,该文件中的每一行都包含一条记录。

记录按以下格式保存:

<MovieID>::<Title>::<Genres>

MovieId 是一个简单的数字整数序列。Title是一个字符串,其中包括电影的发行年份,在其名称后面的括号中指定。电影标题与IMDB(www. imdb.com)中的标题相同。每部电影可以分为多种类型,这些类型以管道分隔的格式指定。文件中的示例行如下所示:

1::Toy Story (1995)::Animation|children's|Comedy

ratings.dat文件包含超过6,000用户的3,952部电影的评级。评级文件包含超过 100 万条记录。每行都是一条不同的记录,其中包含以下格式的数据:

UserID: :MovieID: :Rating::Timestamp

用户 ID 和影片 ID 分别标识并建立与用户和影片的关系。该评级是 5 分(5 星)等级的衡量标准。时间戳捕获记录评级的时间。

users.dat文件包含有关对电影进行分级的用户的数据。超过6,000个用户的信息以以下格式记录:

UserID: :Gender::Age::occupation::zip-code

查询数据

为简单起见,将数据上传到三个MongoDB集合中:电影,评级和用户,每个集合映射到.dat数据文件。mongoimport实用程序www.mongodb.org/display/Docs/ Import+Export+Tools适合从.dat文件中提取数据并将其加载到MongoDB文档存储中,但这不是一个选项。MovieLens 数据由双冒号 (: :)字符和 mongoimport 仅识别 JSON、逗号分隔和制表符分隔的格式。

因此,我依靠编程语言和关联的MongoDB驱动程序来帮助解析文本文件并将数据集加载到MongoDB集合中。为了简洁起见,我选择了Ruby。或者,您可以使用Python,Java,PHP,C或任何其他支持的语言。

一小段代码,如示例所示,可以轻松地从用户中提取和加载电影数据,并将评级数据文件发送到相应的 MongoDB 集合。此代码使用简单的文件读取和字符串拆分功能,以及MongoDB驱动程序来执行任务。虽然这段代码写的不是很优雅,但是可以快速处理大量的数据。

image.png image.png

加载数据后,即可运行一些查询来对其进行切片和切块。

可以从 JavaScript shell 或任何受支持的语言运行查询。

对于此示例,我使用 JavaScript shell 运行大多数查询,并使用几种不同的编程语言及其各自的驱动程序运行少数查询。包含编程语言示例的唯一目的是证明通过JavaScript shell实现的大多数(如果不是全部)功能都可以通过不同的语言驱动程序获得。

要开始查询MongoDB集合,请启动MongoDB服务器并使用Mongo shell连接到它。可以从MongoDB安装的bin文件夹中访问必要的程序。

在 Mongo JavaScript shell 上,首先获取 rating 集合中所有值的计数,如下所示:

db.ratings.count();

作为响应,您应该看到1000209。上传了一百多万的评分,所以这看起来是正确的。接下来,借助以下命令获取评分数据的示例集:

db.ratings.find();

在 shell 上,不需要显式光标即可打印出集合中的值。shell 将行数限制为一次最多 20 行。要遍历更多数据,只需在 shell 上键入它(迭代的缩写)。作为对 it 命令的响应,如果除了已在 shell 上浏览的记录之外,还存在更多记录,则应该会看到 20 多条记录和一个标有“has more”的标签。

评分数据,例如,{“_id” : objectId(“4cdcf1ea5a918708b0000001”),“user_ id” : 1, “movie_id” : 1193, “rating” : 5, “timestamp” : “978300760” },对它相关的电影几乎没有直观的意义,因为它链接到电影 ID 而不是它的名字。您可以通过回答以下问题来解决此问题:

如何获取给定电影的所有评分数据?如何获取给定评级的电影信息?

如何将所有电影列表放在一起,并按与电影相关的电影分组的评级数据?

在关系数据库中,这些类型的关系是使用连接遍历的。在MongoDB中,这种关系数据在服务器范围之外明确地相互关联。MongoDB定义了DBRef的概念,以在两个独立集合的两个字段之间建立关系,但该功能有一些限制,并且不能提供与显式基于id的链接相同的功能。

若要获取给定电影的所有分级数据,可以使用电影 ID 作为条件筛选数据集。

例如,要查看著名的奥斯卡获奖电影《泰坦尼克号》的所有评分,您需要首先找到其 id,然后使用它来过滤评分集合。如果您不确定“泰坦尼克号”的确切标题字符串是什么样的,但您确信泰坦尼克号一词出现在其中,您可以尝试与电影集合中的标题字符串进行近似匹配,而不是精确匹配。

在 RDBMS 中,要在这种情况下查找电影 ID,您可能会依赖 SQL where 子句中的 like 表达式来获取所有可能候选项的列表。在MongoDB中,没有类似的表达式,但有一个更强大的功能可用,即使用正则表达式定义模式的能力。因此,要获取电影集合中标题中包含泰坦尼克号或泰坦尼克号的所有记录的列表,您可以像这样查询:

db.movies.find({ title:/titanic/i});

此查询返回以下文档集:

{ "_id" :1721, "title" : "Titanic (1997)", "genres":["Drama","Romance"1]}

{ "_id" : 2157, "title" :"Chambermaid on the Titanic, The (1998)", "genres": "Romance"}

{ "_id" : 3403, "title" : "Raise the Titanic (1980)", "genres":[ "Drama", "Thriller"]}

{"_id" : 3404, "title" : "Titanic (1953)", "genres":[ "Action","Drama"]}

数据集中的标题字段包括电影的发行年份。在标题字段中,发布年份包含在括号中。因此,如果您记得或碰巧知道泰坦尼克号是在 1997 年发布的,您可以编写一个更优化的查询表达式,如下所示:

db.movies.find({ title: /titanic.*\(1997\).*/i});

这将只返回一个文档:

("_id" : 1721, "title" : "Titanic (1997)", "genres" :[ "Drama","Romance"])

表达式实质上查找包含泰坦尼克号、泰坦尼克号、泰坦尼克号或 TiTAnic 的所有标题字符串。简而言之,它忽略了大小写。此外,它还查找字符串 (1997)。它还指出,在泰坦尼克号和(1997)和之后(1997)之间可能有O或更多的字符。对正则表达式的支持是一个强大的功能,掌握它们总是值得的。

分级集合的movie_id字段的值范围由电影集合的the_id定义。因此,要获取 id 为 1721 的电影泰坦尼克号的所有评分,您可以像这样查询:

db.ratings.find({ movie_id:1721});

要找出泰坦尼克号的可用评级数量,您可以按如下方式计算它们:

db.ratings.find({ movie_id:1721 }).count();

对计数的响应是 1546。评分为5分制。要获取电影《泰坦尼克号》的 5 星评级列表和计数,您可以进一步过滤记录集,如下所示:

`db.ratings.find({ movie_id: 1721, rating:5});

db.ratings.find({ movie_id: 1721, rating:5 }).count();`

接下来,您可能想获得泰坦尼克号所有评级的一些统计数据。若要找出用户的不同分级集(从 1 到 5 之间的可能整数集,包括 1 和 5),可以按如下方式进行查询:

db.runcommand({ distinct: 'ratings', key: 'rating', query: { movie_id:1721} });

泰坦尼克号的评级包括 1 到 5 之间的所有可能情况(包括 1 和 5)(包括),因此响应如下所示:

( "values" : [ 1, 2, 3, 4,5 ], "ok":1}

runcommand 采用以下参数:

- 标记为非重复字段的集合名称

- 键的字段名称,将列出其非重复值 

- 查询以选择性地筛选集合

runCommand 在模式上与您目前看到的查询样式略有不同,因为在搜索非重复值之前会筛选集合。集合中所有评级的不同值可以按您目前看到的方式列出,如下所示:

db.ratings.distinct(“rating”);

您从不同的值中知道泰坦尼克号具有从 1 到 5 的所有可能评级。要查看这些评级如何按 5 分制的每个评级值进行细分,您可以按如下方式对计数进行分组:

image.png

此分组查询的输出是一个数组,如下所示:

 [{"rating":4, "count":500 },{"rating":1, "count":100 },{"rating":5, "count":389 },{"rating":3, "count":381 },{"rating":2,"count":176} ]

这种按功能分组对于单个MongoDB实例非常方便,但在分片部署中不起作用。使用MongoDB的MapReduce工具在分片MongoDB设置中运行分组函数。在解释分组操作之后,立即包含分组函数的MapReduce版本。

组操作将对象作为输入。此组操作对象包括以下字段:

  • key - 要作为分组依据的文档字段。前面的示例只有一个字段:rat- ing。其他分组依据字段可以包含在逗号分隔的列表中,并分配为键字段的值。可能的配置可以是 - key: ( fielda: true, fieldB: true}

  • initial - 聚合统计信息的初始值。在前面的示例中,初始计数设置为 0。

  • cond-用于筛选集合的查询文档。

  • reduce-聚合函数。

  • keyf(可选)- 如果所需键不是现有文档字段,则为备用派生键。

  • finalize (可选) - 一个函数,可以在 reduce 函数循环访问的每个项目上运行。这可用于修改现有项目。

从理论上讲,该示例可以很容易地演变成这样一种情况,即只需使用以下分组操作即可按评分点对每部电影的评级进行分组:

image.png

但是,在实际情况下,这不适用于 100 万个项目的评级集合。您将看到以下错误消息:

image.png

结果作为单个 BSON 对象返回,因此应用组操作的集合不应超过 10,000 个键。这个限制也可以用MapReduce工具克服。


本文正在参加「金石计划 . 瓜分6万现金大奖」