1. 程式人生 > >淺嘗輒止MongoDB:高階查詢

淺嘗輒止MongoDB:高階查詢

目錄

二、聚合

2. 求和

3. 求平均

4. 除錯

一、全文檢索

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日誌檔案中。在除錯時,這是一個有價值的工具。