淺嘗輒止MongoDB:高階查詢
目錄
一、全文檢索
1. 建立索引
MongoDB一個集合上只能建立一個文字索引。
建立文字索引:在集合texttest上的body鍵上建立文字索引。
db.texttest.createIndex( { body : "text" } );
指定索引的預設語言:
db.texttest.createIndex( { body : "text" }, { default_language : "french" } );
在多種語言上建立索引:同一集合中存在多種語言,需要有一個欄位標記每個文件的語言,如下面的四個文件中的lingvo欄位標識其語言。
{ _id : 1, content : "cheese", lingvo : "english" }
{ _id : 2, content : "fromage", lingvo: "french" }
{ _id : 3, content : "queso", lingvo: "spanish" }
{ _id : 4, content : "ost", lingvo: "swedish" }
使用文件中給定的語言建立索引:
db.textExample.createIndex( { content : "text" }, { language_override : "lingvo" } );
建立符合索引:同時索引content和comments欄位,可以在這兩個欄位上進行文字搜尋。
db.textExample.createIndex( { content : "text", comments : "text" });
使用萬用字元:在全部欄位上建立索引,並命名索引。
db.textExample.createIndex( { "$**": "text" }, { name: "alltextindex" } );
指定權重:指定content的權重是10,comments權重是5,其它欄位的權重為1。
db.textExample.createIndex( { content : "text", comments : "text"}, { weights : { content: 10, comments: 5, } } );
同時建立文字和非文字的複合索引:content上建立文字索引,username上建立普通索引。
db.textExample.createIndex( { content : "text", username : 1 });
2. 執行搜尋
文字搜尋:以fish為詞根進行搜尋,返回body中匹配fish字串的文件。
db.texttest.find({ $text : { $search :"fish" } });
過濾結果:在文字匹配的文件中過濾出about鍵值為food的結果。
db.texttest.find({ $text : { $search : "fish" }, about : "food" });
複雜搜尋:返回文件中body鍵匹配cook,但不匹配lunch的body值。先搜尋所有匹配條件的資料,再刪除不匹配的資料。
db.texttest.find({ $text : { $search : "cook -lunch" } }, {_id:0, body:1});
字面搜尋:返回body鍵匹配整個字串mongodb text search,而不是匹配mongodb、text、search這三個單詞的文件。
db.texttest.find({ $text : { search : "\"mongodb text search\"" } });
限制返回的文件數:返回1條。
db.texttest.find({ $text : { $search :"fish" }}).limit(1);
顯示指定元素:只顯示body。
db.texttest.find({ $text : { $search :"fish"}}, { _id : 0, body : 1 });
指定文字搜尋使用的語言:全小寫方式指定。
db.texttest.find({ $text : { $search :"fish", $language : " french" } });
利用文字與非文字的複合索引優化查詢:
db.texttest.createIndex( { about : 1, body : "text" });
db.texttest.find({ $text : { $search : "fish"}, about : "food"}).explain("executionStats").executionStats;
二、聚合
db.collection.aggregate( { $group : { _id : "$color" } } );
類比SQL:
select distinct color from collection;
-- 或
select color from collection group by color;
db.collection.aggregate({ $group : { _id : "$color", count : { $sum : 1 } } });
類比SQL:
select color, count(1) count from collection group by color;
db.collection.aggregate({ $group : { _id : { color: "$color", transport: "$transport"} , count : { $sum : 1 } } });
類比SQL:
select color transport, count(1)
from collection
group by color, transport;
db.collection.aggregate(
[
{ $group : { _id : { color: "$color", transport: "$transport"} , count : { $sum : 1 } } },
{ $limit : 5 }
]);
類比SQL:
select color, transport, count(1)
from collection
group by color, transport
limit 5;
db.collection.aggregate(
[
{ $match : { num : { $gt : 500 } } },
{ $group : { _id : { color: "$color", transport: "$transport"} , count : { $sum : 1 } } },
{ $limit : 5 }
]);
類比SQL:
select color, transport, count(1)
from collection
where num > 500
group by color, transport
limit 5;
db.collection.aggregate(
[
{ $group : { _id : { color: "$color", transport: "$transport"} , count : { $sum : 1 } } },
{ $sort : { _id :1 } },
{ $limit : 5 }
]);
類比SQL:
select color, transport, count(1)
from collection
group by color, transport
order by color, transport
limit 5;
db.collection.aggregate(
[
{ $match : { num : { $gt : 500 } } },
{ $group : { _id : { color: "$color", transport: "$transport"} , count : { $sum : 1 } } },
{ $sort : { _id :1 } },
{ $limit : 1 }
]);
類比SQL:
select color, transport, count(1)
from collection
where num > 500
group by color, transport
order by color, transport
limit 1;
db.collection.aggregate( { $unwind : "$vegetables" });
類比SQL:
select collection.*, substring_index(substring_index(vegetables, ',', id),',' ,-1) vegetables
from collection, nums -- nums為只有id一列的數字輔助表
where id <= length(vegetables)-length(replace(vegetables,',',''))+1;
db.collection.aggregate(
[
{ $unwind : "$vegetables" },
{ $project : { _id: 0, fruits:1, vegetables:1 } }
]);
類比SQL:
select fruits, substring_index(substring_index(vegetables, ',', id),',' ,-1) vegetables
from collection, nums -- nums為只有id一列的數字輔助表
where id <= length(vegetables)-length(replace(vegetables,',',''))+1;
db.collection.aggregate(
[
{ $unwind : "$vegetables" },
{ $project : { _id: 0, fruits:1, veggies: "$vegetables" } }
]);
類比SQL:
select fruits, substring_index(substring_index(vegetables, ',', id),',' ,-1) veggies
from collection, nums -- nums為只有id一列的數字輔助表
where id <= length(vegetables)-length(replace(vegetables,',',''))+1;
db.collection.aggregate(
[
{ $unwind : "$vegetables" },
{ $project : { _id: 0, fruits:1, vegetables:1 } },
{ $skip : 2995 }
]);
類比SQL:
select fruits, substring_index(substring_index(vegetables, ',', id),',' ,-1) vegetables
from collection, nums -- nums為只有id一列的數字輔助表
where id <= length(vegetables)-length(replace(vegetables,',',''))+1
limit 2995, 999999999;
db.collection.aggregate(
[
{ $unwind : "$vegetables" },
{ $project : { _id: 0, fruits:1, vegetables:1 } },
{ $skip : 2995 },
{ $out : "food" }
]);
類比SQL:
create table food as
select @a:[email protected]+1 id, fruits, substring_index(substring_index(vegetables, ',', id),',' ,-1) vegetables
from collection, (select @a:=0) t, nums -- nums為只有id一列的數字輔助表
where id <= length(vegetables)-length(replace(vegetables,',',''))+1
limit 2995, 999999999;
db.prima.aggregate(
[
{$lookup: {
from : "secunda",
localField : "number",
foreignField : "number",
as : "secundaDoc"
} },
]);
類比SQL:
select prima.*, concat(secunda.c1,secunda.c2,...secunda.cn) secundaDoc
from prima left join secunda on prima.number = secunda.number;
db.prima.aggregate(
[
{$lookup:{
from : "secunda",
localField : "number",
foreignField : "number",
as : "secundaDoc" }},
{$unwind: "$secundaDoc"},
{$project: {_id : "$number", english:1, ascii:"$secundaDoc.ascii" }}
]);
類比SQL:
select prima.*, secunda.*
from prima left join secunda on prima.number = secunda.number;
三、MapReduce
MongoDB通過兩個使用者自定義的JavaScript函式實現查詢:map和reduce。MongoDB將對指定的集合執行一個專門的查詢,所有匹配該查詢的文件都將被輸入到map函式中。map函式被設計用於生成鍵值對。任何含有多個值的鍵都將被輸入到reduce函式中,reduce函式將返回輸入資料的聚合結果。最後,還有一個可選步驟,通過finalize函式對資料的顯示進行完善。
以下是來自文件的圖,可以清楚的說明 Map-Reduce 的執行過程。
1. 最簡MapReduce
定義map函式:
var map = function() {
emit(this.color, this.num);
};
MongoDB中使用emit函式向MapReduce提供Key/Value對。map函式接收集合中的color和num欄位作為輸入,輸出為以color為鍵,以num陣列為值的文件。
定義空reduce函式:
var reduce = function(color, numbers) { };
reduce函式接收map傳來的鍵值對,但不執行任何操作。
執行MapReduce:
db.mapreduce.mapReduce(map,reduce,{ out: { inline : 1 } });
{ out : { inline : 1 } } 表示將執行結果輸出到控制檯,顯示類似如下的結果。
{
"results" : [
{
"_id" : "black",
"value" : null
},
{
"_id" : "blue",
"value" : null
},
...
{
"_id" : "yellow",
"value" : null
}
],
"timeMillis" : 95,
"counts" : {
"input" : 1000,
"emit" : 1000,
"reduce" : 55,
"output" : 11
},
"ok" : 1,
}
結果顯示,為每種顏色建立了一個單獨的文件,並且使用顏色作為文件的唯一_id值。因為reduce函式體為空,所以value被設定為null。
2. 求和
定義求和reduce函式:
var reduce = function(color, numbers) {
return Array.sum(numbers);
};
該reduce函式對每個color對應的多個num求和。
執行MapReduce,並將結果輸出到集合mrresult中:
db.mapreduce.mapReduce(map,reduce,{ out: "mrresult" });
檢視結果集合:
> db.mrresult.findOne();
{ "_id" : "black", "value" : 45318 }
3. 求平均
map函式:
var map = function() {
var value = {
num : this.num,
count : 1
};
emit(this.color, value);
};
count為計數器,為了只統計每個文件一次,將count值設定為1。
reduce函式:
var reduce = function(color, val ) {
reduceValue = { num : 0, count : 0};
for (var i = 0; i < val.length; i++) {
reduceValue.num += val[i].num;
reduceValue.count += val[i].count;
}
return reduceValue;
};
用一個簡單的迴圈對num和count求和。注意reduce函式中return函式返回的值,必須與map函式中傳送到emit函式中的value結構相同。
finalize函式:
var finalize = function (key, value) {
value.avg = value.num/value.count;
return value;
};
finalize函式從reduce函式接收結果,並計算平均值。
執行:
db.mapreduce.mapReduce(map,reduce,{ out: "mrresult", finalize : finalize });
檢視結果:
> db.mrresult.findOne();
{
"_id" : "black",
"value" : {
"num" : 45318,
"count" : 91,
"avg" : 498
}
}
4. 除錯
(1)除錯map函式 過載emit函式,列印map函式的輸出:
var emit = function(key, value) {
print("emit results - key: " + key + " value: " + tojson(value));
}
使用map.apply和樣例文件進行測試:
> map.apply(db.mapreduce.findOne());
emit results - key: blue value: { "num" : 1, "count" : 1 }
(2)除錯reduce函式 首先需要確認map和reduce函式返回結果的格式必須嚴格一致。然後建立一個數組,模擬傳入到reduce函式中的陣列:
a = [{ "num" : 1, "count" : 1 },{ "num" : 2, "count" : 1 },{ "num" : 3, "count" : 1 }]
現在呼叫reduce函式,顯示返回結果:
>reduce("blue",a);
{ "num" : 6, "count" : 3 }
如果出現某些問題,不理解函式中的內容,那麼可以使用printjson()函式將JSON值輸出到mongodb日誌檔案中。在除錯時,這是一個有價值的工具。