使用 rmongodb 在 R 中运行高级 MongoDB 查询

Posted

技术标签:

【中文标题】使用 rmongodb 在 R 中运行高级 MongoDB 查询【英文标题】:Running advanced MongoDB queries in R with rmongodb 【发布时间】:2012-05-29 11:58:31 【问题描述】:

由于 mysql 让我发疯,我正试图让自己熟悉我的第一个“NoSQL”DBMS,它恰好是 MongoDB。我正在通过rmongodb 连接到它。

我使用rmongodb 的次数越多,关于运行高级查询的问题/问题就越多。

在详细介绍我似乎无法正确指定的不同类型的查询之前,我首先提供一些示例数据。

示例数据

例子取自MongoDB website,做了一点简化。

pkg <- "rmongodb"
if (!require(pkg, character.only=TRUE)) 
    install.packages(pkg)
    require(pkg, character.only=TRUE)   


# Connect to DB
db <- "test"
ns <- "posts"
mongo <- mongo.create(db=db)

# Insert document to collection 'test.users'
b <- mongo.bson.from.list(list(
    "_id"="alex", 
    name=list(first="Alex", last="Benisson"),
    karma=1.0,
    age=30,
    test=c("a", "b")
))
mongo.insert(mongo, "test.users", b)

# Insert document to collection 'test.posts'
b <- mongo.bson.from.list(list(
        "_id"="abcd",
        when=mongo.timestamp.create(strptime("2011-09-19 02:00:00",
            "%Y-%m-%d %H:%M:%s"), increment=1),
        author="alex",
        title="Some title",
        text="Some text.",
        tags=c("tag.1", "tag.2"),
        votes=5,
        voters=c("jane", "joe", "spencer", "phyllis", "li"),
        comments=list(
            list(
                who="jane", 
                when=mongo.timestamp.create(strptime("2011-09-19 04:00:00",
                    "%Y-%m-%d %H:%M:%s"), increment=1),
                comment="Some comment."
            ),
            list(
                who="meghan", 
                when=mongo.timestamp.create(strptime("2011-09-20 13:00:00",
                    "%Y-%m-%d %H:%M:%s"), increment=1),
                comment="Some comment."
            )
        )
    )
)
b
mongo.insert(mongo, "test.posts", b)

关于插入 JSON/BSON 对象的两个问题:

    文档'test.posts',字段voters:在这种情况下使用c()是否正确? 文档“test.posts”,字段comments:指定此内容的正确方法是什么,c()list()

***查询:它们很管用

***查询工作正常:

# Get all posts by 'alex' (only titles)
res <- mongo.find(mongo, "test.posts", query=list(author="alex"), 
    fields=list(title=1L))
out <- NULL
while (mongo.cursor.next(res))
    out <- c(out, list(mongo.bson.to.list(mongo.cursor.value(res))))

> out
[[1]]
                       _id                      title 
                     "abcd"            "No Free Lunch" 

问题一:基本子级查询

如何运行一个简单的“子级查询”(与***查询相反),该查询需要进入JSON/BSON 样式 MongoDB 对象的任意深度子级?这些子级别查询使用 MongoDB 的 dot notation,我似乎无法弄清楚如何将其映射到有效的 rmongodb 查询

在简单的 MongoDB 语法中,类似于

> db.posts.find(  comments.who : "meghan"  )

会工作的。但我不知道如何使用rmongodb 函数来做到这一点

这是我目前尝试的方法

# Get all comments by 'meghan' from 'test.posts'

#--------------------
# Approach 1)
#--------------------
res <- mongo.find(mongo, "test.posts", query=list(comments=list(who="meghan")))
out <- NULL
while (mongo.cursor.next(res))
    out <- c(out, list(mongo.bson.to.list(mongo.cursor.value(res))))

> out
NULL
# Does not work

#--------------------
# Approach 2) 
#--------------------
buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "comments")
mongo.bson.buffer.append(buf, "who", "meghan")
mongo.bson.buffer.finish.object(buf)
query <- mongo.bson.from.buffer(buf)
res <- mongo.find(mongo, "test.posts", query=query)
out <- NULL
while (mongo.cursor.next(res))
    out <- c(out, list(mongo.bson.to.list(mongo.cursor.value(res))))

> out
NULL
# Does not work

问题 2:使用 $ 运算符的查询

这些工作

查询 1

buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "age")
mongo.bson.buffer.append(buf, "$lte", 30)
mongo.bson.buffer.finish.object(buf)
criteria <- mongo.bson.from.buffer(buf)
criteria

> mongo.find.one(mongo, "test.users", query=criteria)
    _id : 2      alex
    name : 3     
        first : 2    Alex
        last : 2     Benisson

    karma : 1    1.000000
    age : 1      30.000000
    test : 4     
        0 : 2    a
        1 : 2    b

查询 2

buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "test")
mongo.bson.buffer.append(buf, "$in", c("a", "z"))
mongo.bson.buffer.finish.object(buf)
criteria <- mongo.bson.from.buffer(buf)
criteria
mongo.find.one(mongo, "test.users", query=criteria)

但是,请注意,原子集将导致返回值NULL

mongo.bson.buffer.append(buf, "$in", "a")
# Instead of 'mongo.bson.buffer.append(buf, "$in", c("a", "z"))'

尝试与子级别查询相同,我又迷路了

buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "name")
mongo.bson.buffer.start.object(buf, "first")
mongo.bson.buffer.append(buf, "$in", c("Alex", "Horst"))
mongo.bson.buffer.finish.object(buf)
mongo.bson.buffer.finish.object(buf)
criteria <- mongo.bson.from.buffer(buf)
criteria <- mongo.bson.from.buffer(buf)
> criteria
    name : 3     
        first : 3    
            $in : 4      
                0 : 2    Alex
                1 : 2    Horst

> mongo.find.one(mongo, "test.users", query=criteria)
NULL

【问题讨论】:

作者 (Gerald Lindsly) 在这里发布了一个简短的博客 - r-bloggers.com/rmongodb-r-driver-for-mongodb。它有点老了,但他似乎热衷于反馈,所以值得给他发一封电子邮件(他的地址在链接的底部)? AFAIK 驱动程序是社区开发的驱动程序之一。 谢谢马克,我确实浏览了帖子,但只是看到它有一个相当广泛的示例应用程序链接。是的,它是社区开发的。我最终确实尝试联系 Gerald,但也认为将其放在 SO 上是个好主意。 酷,我正在看它,但我对 R 很陌生,所以我怀疑我能帮上什么忙。让我知道你过得怎么样。 【参考方案1】:

c() 或 list() 都可以。取决于组件是否命名以及它们是否都具有相同的类型(对于列表)。最好的办法是查看生成的 BSON,看看你是否得到了你想要的。为了最好地控制生成的对象,请使用 mongo.bson.buffer 和对其进行操作的函数。事实上,这就是子查询失败的原因。 'cmets' 被创建为子对象而不是数组。 mongo.bson.from.list() 很方便,但它不能为您提供相同的控制,有时它会猜错从复杂结构中生成的内容。

可以像这样更正对另一组数据的查询:

buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "name.first")
mongo.bson.buffer.append(buf, "$in", c("Alex", "Horst"))
mongo.bson.buffer.finish.object(buf)
criteria <- mongo.bson.from.buffer(buf)

请注意,您肯定需要在此处使用缓冲区,因为 R 会因虚线名称而窒息。

我希望这能解决您的问题。如果您有任何其他问题,请告诉我。

【讨论】:

太好了,非常感谢杰拉德!明天我会用我的代码检查一下。 我尝试一次解决一件事,但似乎仍然无法为 posts 集合找出正确的 BSON 结构;-) 我在尝试的地方添加了一个“答案”遵循您的建议,让您尽可能轻松地破解它。非常感谢您的支持! 另外:我怎么知道我最终是一个子对象还是一个数组? R 中生成的 BSON 对象在这两种情况下看起来都非常相似。【参考方案2】:

我仍然不太清楚,一旦发布了问题,在 SO 上进展的首选方式是什么,但有人希望详细说明一下,可能会添加更多的问题和回答方法。

正如我经常被告知不要在未来的编辑中炸毁我最初的问题,在这个“答案”中,我只是采纳了 Gerald Lindsly 的建议并尝试将其放入实际代码中(因为它仍然没有工作替我出去):

准备工作

pkg <- "rmongodb"
if (!require(pkg, character.only=TRUE)) 
    install.packages(pkg)
    require(pkg, character.only=TRUE)   


# Connect to DB
db <- "test"
ns <- "posts"
mongo <- mongo.create(db=db)

# Make sure we start with an empty collection
mongo.drop(mongo, paste(db, ns, sep="."))

插入文档

正如 Gerald 在他的回答中指出的那样,mongo.bson.from.list() 有时会对生成的 BSON 结构做出错误的猜测,因此我尝试继续显式创建 BSON 数组对象:

buf <- mongo.bson.buffer.create()

# 'REGULAR' APPENDING
mongo.bson.buffer.append(buf, "_id", "abcd")
mongo.bson.buffer.append(buf, "when", 
    mongo.timestamp.create(strptime("2011-09-19 02:00:00",
        "%Y-%m-%d %H:%M:%s"), increment=1))
mongo.bson.buffer.append(buf, "author", "alex")
mongo.bson.buffer.append(buf, "title", "Some title")
mongo.bson.buffer.append(buf, "text", "Some text.")
mongo.bson.buffer.append(buf, "tags", c("tag.1", "tag.2"))
mongo.bson.buffer.append(buf, "votes", 5)
# /

# VOTERS ARRAY
mongo.bson.buffer.start.array(buf, "voters")
voters <- c("jane", "joe", "spencer", "phyllis", "li")
i=1
for (i in seq(along=voters)) 
    mongo.bson.buffer.append(buf, as.character(i), voters[i])

mongo.bson.buffer.finish.object(buf)
# /

# COMMENTS ARRAY
mongo.bson.buffer.start.array(buf, "comments")

mongo.bson.buffer.start.object(buf, "1")
mongo.bson.buffer.append(buf, "who", "jane")
mongo.bson.buffer.append(buf, "when", 
    mongo.timestamp.create(strptime("2011-09-19 04:00:00",
            "%Y-%m-%d %H:%M:%s"), increment=1))
mongo.bson.buffer.append(buf, "comment", "some comment.")
mongo.bson.buffer.finish.object(buf)

mongo.bson.buffer.start.object(buf, "2")
mongo.bson.buffer.append(buf, "who", "meghan")
mongo.bson.buffer.append(buf, "when", 
    mongo.timestamp.create(strptime("2011-09-20 13:00:00",
            "%Y-%m-%d %H:%M:%s"), increment=1))
mongo.bson.buffer.append(buf, "comment", "some comment.")
mongo.bson.buffer.finish.object(buf)
# /

# FINALIZE
mongo.bson.buffer.finish.object(buf)
b <- mongo.bson.from.buffer(buf)
> b
_id : 2      abcd
when : 17    i: 1, t: 1316390400
author : 2   alex
title : 2    Some title
text : 2     Some text.
tags : 4     
    0 : 2    tag.1
    1 : 2    tag.2

votes : 1    5.000000
voters : 4   
    1 : 2    jane
    2 : 2    joe
    3 : 2    spencer
    4 : 2    phyllis
    5 : 2    li

comments : 4     
    1 : 3    
        who : 2      jane
        when : 17    i: 1, t: 1316397600
        comment : 2      some comment.

    2 : 3    
        who : 2      meghan
        when : 17    i: 1, t: 1316516400
        comment : 2      some comment.

mongo.insert(mongo, "test.posts", b)

基本的子级查询

# Get all comments by 'meghan' from 'test.posts'

#--------------------
# Approach 1)
#--------------------
res <- mongo.find(mongo, "test.posts", query=list(comments=list(who="meghan")))
out <- NULL
while (mongo.cursor.next(res))
    out <- c(out, list(mongo.bson.to.list(mongo.cursor.value(res))))

> out
NULL
# Does not work

#--------------------
# Approach 2) 
#--------------------
buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "comments")
mongo.bson.buffer.append(buf, "who", "meghan")
mongo.bson.buffer.finish.object(buf)
query <- mongo.bson.from.buffer(buf)
res <- mongo.find(mongo, "test.posts", query=query)
out <- NULL
while (mongo.cursor.next(res))
    out <- c(out, list(mongo.bson.to.list(mongo.cursor.value(res))))

> out
NULL
# Does not work

在指定文档时,我仍然必须在这里做错事;-)

【讨论】:

【参考方案3】:

关于原子查询和 $in 运算符,我从您的第一个问题中得到了查询 2,其工作方式如下:

buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "test")
mongo.bson.buffer.start.array(buf, "$in")
mongo.bson.buffer.append(buf, "a", "a")
mongo.bson.buffer.finish.object(buf)
mongo.bson.buffer.finish.object(buf)
criteria <- mongo.bson.from.buffer(buf)
criteria

如果数组最终只包含一个元素,我想明确地开始和结束数组就可以了。

可能有用的一件事是监控 mongod 控制台或日志(在使用 -v 选项启动 mongod 之后)。运行旧查询,您会看到:

Tue Nov 20 16:09:04 [conn23] User Assertion: 12580:invalid query
Tue Nov 20 16:09:04 [conn23] assertion 12580 invalid query ns:test.users query: test:  $in: "a"  
Tue Nov 20 16:09:04 [conn23] problem detected during query over test.users :  $err: "invalid query", code: 12580 
Tue Nov 20 16:09:04 [conn23] query test.users query:  test:  $in: "a"   ntoreturn:0 keyUpdates:0 exception: invalid query code:12580 locks(micros) r:440 reslen:59 0ms

运行修改后的查询,看起来没问题:

Tue Nov 20 16:10:14 [conn23] query test.users query:  test:  $in: [ "a" ]   ntoreturn:0 keyUpdates:0 locks(micros) r:168 nreturned:1 reslen:142 0ms

【讨论】:

看起来很有希望,谢谢!已经等了很久了,我明天去看看!

以上是关于使用 rmongodb 在 R 中运行高级 MongoDB 查询的主要内容,如果未能解决你的问题,请参考以下文章

使用 rmongodb 或 RMongo 在 R 中获取数据统计信息

rmongodb + 闪亮 --> 错误 15

r - rmongodb $or 查询构造

rmongodb:在查询中使用 $or

使用 rmongodb 和 plyr 将大型 MongoDB 集合传输到 R 中的 data.frame

对 MongoDB 3 的 rmongodb 支持