MongoDB Upsert (create exist Ignore) 實作

2022/10/03 Updated…

看到以前的文章,發現把這功能想得太複雜了,通常在批量調整索引時,數量也不會太多,直接從系統取得 Index 名稱,再進行刪除或建立即可。

1
2
3
4
5
db.getCollection("your.collection").getIndexes().forEach(doc => {
if(doc.name.indexOf("_id") != -1) return;
// or if(doc.name == "_id_" ) return;
db.getCollection("your.collection").dropIndex(doc.name);
});

非常簡單明瞭要做什麼。
雖然之前寫的做法可以開發出不同功能,不過簡單操作的話,上面這一段即可。


最近因為部署有些狀況,反而需要因應各環境 index 不同,分別有不同的調整,查了一下 MongoDB 並沒有類似的語法,只要是

  • Create 時名稱重複
  • Drop 不存在的 index

都會噴錯,導致後續語法不能繼續執行,因此自己寫了一個概念類似 upsert index 或者像是 insert ignore 的方法。

思路

  • 建立時,遇到
    • 名稱不重複:
      • 欄位一樣,可刪除舊的或者忽略新的
      • 欄位不同,建立新的
    • 名稱重複:
      • 欄位一樣,略過此建立
      • 欄位不同,先 drop 舊的再建新的

看起來是蠻簡單的,就來確認各項功能。


Step1 取得現有 index

這個應該是相對簡單一點,我們只需擷取 keyNamefields,並回傳結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
getCurrentIndexes: function (){

let that = this;
var bson = db.getCollection(`testCollection`).getIndexes();
var json = JSON.stringify(bson);
var obj = JSON.parse(json);
var dict = new Object();
dict = {};

for(var itr = 0 ; itr < obj.length ; itr++)
{
dict[obj[itr].name] = JSON.stringify(obj[itr].key);
}

return dict;
}

dropIndex

畢竟會需要刪除舊的,因此也要做上這段

1
2
3
4
5
6
7
8
dropIndex: function(toDeletedIndexNames){
let that = this;

db.runCommand({
dropIndexes: `testCollection`,
index: toDeletedIndexNames
});
}

createIndex

這邊就沒特別帶 index 參數近來,直接使用上一層作用域的變數 readyToCreateIndexes

1
2
3
4
5
6
7
8
9
createIndex: function(){
let that = this;
if (this.readyToCreateIndexes.length == 0) return;

db.runCommand({
createIndexes: `testCollection`,
index: this.readyToCreateIndexes
});
}

主邏輯,確認重複的 index

前面簡單的寫完了,接下來進到要實作邏輯部分,我們先傳入已經存在的索引 dict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
getExistedIndexes: function (dict){

let that = this;
var needDeleteIndexes = [];
var spliceIndex = [];


// 外層迴圈逐一確認想新增的 index
for(var itr in this.readyToCreateIndexes){
let createName = this.readyToCreateIndexes[itr].name;
let createKey = JSON.stringify(this.readyToCreateIndexes[itr].key);

// 內層迴圈逐一走過現有的 index
for(var keyName in dict){

// 名稱與欄位都相同,略過此新增 index,並記錄在 spliceIndex 內
if (createName == keyName && createKey == dict[keyName]) {
print(`1. ${createName}, ${createKey}`);
spliceIndex.push(itr);
continue;
}

// 名稱相同,欄位不同,代表要更新,於是把舊的 Index name 先暫存在 `needDeleteIndexes`
if (createName == keyName && createKey != dict[keyName]) {
print(`2. ${createName}, ${createKey}`);
needDeleteIndexes.push(createName);
continue;
}

// 名稱不同,欄位相同
// 這是這次的大雷,因為 MongoDB 欄位相同,名稱不同的話一樣不給新增,想一想也是合理
// 這種狀況就是忽略新的 index (視需求決定)
if (createName != keyName && createKey == dict[keyName]) {
print(`3. ${createName}, ${createKey}`);
needDeleteIndexes.push(keyName);
continue;
}
}
}

// 這段是移除不需要新增的 Index
// offset 功能就是整個陣列被刪除元素後的補償
let offset = 0;
for(var toSkip in spliceIndex){
toSkip -= offset;
this.readyToCreateIndexes.splice(toSkip,1);
offset++;
}

return needDeleteIndexes;
}

組合

接下來把全部都串再一起就大功告成囉!!

1
2
3
4
5
6
7
8
9
10
11
12
13
let that = this;

// Get current Indexes
let dict = this.getCurrentIndexes();

// Get duplicated with exist index name
var toDeletedIndexNames = this.getExistedIndexes(dict);

// Drop duplicated indexes
this.dropIndex(toDeletedIndexNames);

// Create indexes
this.createIndex();

完整語法可參閱我的 gist

  • 作者: MingYi Chou
  • 版權聲明: 轉載不用問,但請註明出處!本網誌均採用 BY-NC-SA 許可協議。