MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Nhóm các giá trị và số lượng riêng biệt cho từng thuộc tính trong một truy vấn

Có các cách tiếp cận khác nhau tùy thuộc vào phiên bản có sẵn, nhưng về cơ bản tất cả đều chia nhỏ để chuyển các trường tài liệu của bạn thành các tài liệu riêng biệt trong một "mảng", sau đó "giải nén" mảng đó bằng $ unwind và thực hiện liên tiếp $ group các giai đoạn để tích lũy tổng đầu ra và mảng.

MongoDB 3.4.4 trở lên

Các bản phát hành mới nhất có các toán tử đặc biệt như $ arrayToObject $ objectToArray điều này có thể làm cho việc chuyển sang "mảng" ban đầu từ tài liệu nguồn năng động hơn so với các bản phát hành trước đó:

db.profile.aggregate([
  { "$project": { 
     "_id": 0,
     "data": { 
       "$filter": {
         "input": { "$objectToArray": "$$ROOT" },
         "cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
       }   
     }
  }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": "$data",
    "total": { "$sum": 1 }  
  }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": { "$push": { "k": "$_id", "v": "$v" } }
  }},
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": "$data"
    }
  }}
])

Vì vậy, sử dụng $ objectToArray bạn tạo tài liệu ban đầu thành một mảng gồm các khóa và giá trị của nó dưới dạng "k" "v" các phím trong mảng đối tượng kết quả. Chúng tôi áp dụng $ filter ở đây để chọn bằng "phím". Tại đây bằng cách sử dụng $ in với danh sách các khóa mà chúng tôi muốn, nhưng điều này có thể được sử dụng linh hoạt hơn như một danh sách các khóa để "loại trừ" nơi ngắn hơn. Nó chỉ sử dụng các toán tử logic để đánh giá điều kiện.

Giai đoạn cuối ở đây sử dụng $ ReplaceRoot và vì tất cả các thao tác của chúng tôi và "nhóm" ở giữa vẫn giữ "k" đó và "v" sau đó, chúng tôi sử dụng $ arrayToObject ở đây để quảng bá "mảng đối tượng" của chúng tôi, kết quả là "khóa" của tài liệu cấp cao nhất ở đầu ra.

MongoDB 3.6 $ mergeObjects

Ngoài ra, ở đây, MongoDB 3.6 bao gồm $ mergeObjects có thể được sử dụng như một "bộ tích lũy " trong $ group giai đoạn đường ống cũng vậy, do đó thay thế mã $ push và tạo $ ReplaceRoot cuối cùng chỉ cần chuyển "data" thay vào đó là khóa "gốc" của tài liệu được trả về:

db.profile.aggregate([
  { "$project": { 
     "_id": 0,
     "data": { 
       "$filter": {
         "input": { "$objectToArray": "$$ROOT" },
         "cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
       }   
     }
  }},
  { "$unwind": "$data" },
  { "$group": { "_id": "$data", "total": { "$sum": 1 } }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": {
      "$mergeObjects": {
        "$arrayToObject": [
          [{ "k": "$_id", "v": "$v" }]
        ] 
      }
    }  
  }},
  { "$replaceRoot": { "newRoot": "$data"  } }
])

Điều này không thực sự khác biệt so với những gì đang được trình bày tổng thể, mà chỉ đơn giản là minh họa cách $ mergeObjects có thể được sử dụng theo cách này và có thể hữu ích trong trường hợp khóa nhóm là một cái gì đó khác và chúng tôi không muốn "hợp nhất" cuối cùng đó vào không gian gốc của đối tượng.

Lưu ý rằng $ arrayToObject vẫn cần thiết để chuyển đổi "giá trị" trở lại thành tên của "khóa", nhưng chúng tôi chỉ làm điều đó trong quá trình tích lũy thay vì sau khi nhóm, vì tích lũy mới cho phép "hợp nhất" các khóa.

MongoDB 3.2

Lấy lại phiên bản hoặc ngay cả khi bạn có MongoDB 3.4.x nhỏ hơn phiên bản 3.4.4, chúng tôi vẫn có thể sử dụng phần lớn điều này nhưng thay vào đó chúng tôi xử lý việc tạo mảng theo kiểu tĩnh hơn. khi xử lý "biến đổi" cuối cùng trên đầu ra khác nhau do các toán tử tổng hợp mà chúng tôi không có:

db.profile.aggregate([
  { "$project": {
    "data": [
      { "k": "gender", "v": "$gender" },
      { "k": "caste", "v": "$caste" },
      { "k": "education", "v": "$education" }
    ]
  }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": "$data",
    "total": { "$sum": 1 }  
  }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": { "$push": { "k": "$_id", "v": "$v" } }
  }},
  /*
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": "$data"
    }
  }}
  */
]).map( d => 
  d.data.map( e => ({ [e.k]: e.v }) )
    .reduce((acc,curr) => Object.assign(acc,curr),{})
)

Điều này hoàn toàn giống nhau, ngoại trừ việc thay vì có một biến đổi động của tài liệu thành mảng, chúng tôi thực sự chỉ định "rõ ràng" từng thành viên mảng với cùng một "k" "v" ký hiệu. Thực sự chỉ giữ những tên khóa đó cho quy ước tại thời điểm này vì không có toán tử tổng hợp nào ở đây phụ thuộc vào điều đó cả.

Ngoài ra, thay vì sử dụng $ ReplaceRoot , chúng tôi chỉ làm điều tương tự như những gì triển khai giai đoạn đường ống trước đó đã làm ở đó nhưng thay vào đó là mã máy khách. Tất cả các trình điều khiển MongoDB đều có một số triển khai cursor.map () để bật "chuyển đổi con trỏ". Ở đây với shell, chúng tôi sử dụng các hàm JavaScript cơ bản của Array.map () Array.reduce () để lấy đầu ra đó và một lần nữa quảng bá nội dung mảng trở thành khóa của tài liệu cấp cao nhất được trả về.

MongoDB 2.6

Và quay trở lại MongoDB 2.6 để bao gồm các phiên bản ở giữa, điều duy nhất thay đổi ở đây là việc sử dụng $ map $ đen cho đầu vào với khai báo mảng:

db.profile.aggregate([
  { "$project": {
    "data": {
      "$map": {
        "input": { "$literal": ["gender","caste", "education"] },
        "as": "k",
        "in": {
          "k": "$$k",
          "v": {
            "$cond": {
              "if": { "$eq": [ "$$k", "gender" ] },
              "then": "$gender",
              "else": {
                "$cond": {
                  "if": { "$eq": [ "$$k", "caste" ] },
                  "then": "$caste",
                  "else": "$education"
                }
              }    
            }
          }    
        }
      }
    }
  }},
  { "$unwind": "$data" },
  { "$group": {
    "_id": "$data",
    "total": { "$sum": 1 }  
  }},
  { "$group": {
    "_id": "$_id.k",
    "v": {
      "$push": { "name": "$_id.v", "total": "$total" } 
    }  
  }},
  { "$group": {
    "_id": null,
    "data": { "$push": { "k": "$_id", "v": "$v" } }
  }},
  /*
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": "$data"
    }
  }}
  */
])
.map( d => 
  d.data.map( e => ({ [e.k]: e.v }) )
    .reduce((acc,curr) => Object.assign(acc,curr),{})
)

Vì ý tưởng cơ bản ở đây là "lặp lại" một mảng tên trường được cung cấp, việc gán giá trị thực tế đến bằng cách "lồng" $ cond các câu lệnh. Đối với ba kết quả có thể xảy ra, điều này có nghĩa là chỉ một lồng ghép duy nhất để "phân nhánh" cho mỗi kết quả.

MongoDB hiện đại từ 3.4 có $ switch điều này làm cho việc phân nhánh này đơn giản hơn, nhưng điều này chứng tỏ logic luôn khả thi và $ cond toán tử đã tồn tại kể từ khi khung tổng hợp được giới thiệu trong MongoDB 2.2.

Một lần nữa, sự chuyển đổi tương tự trên kết quả con trỏ cũng được áp dụng vì không có gì mới ở đó và hầu hết các ngôn ngữ lập trình đều có khả năng thực hiện điều này trong nhiều năm, nếu không phải là từ khi mới thành lập.

Tất nhiên, quy trình cơ bản thậm chí có thể được thực hiện trở lại MongoDB 2.2, nhưng chỉ cần áp dụng tạo mảng và $ unwind theo một cách khác. Nhưng không ai nên chạy bất kỳ MongoDB nào dưới 2,8 tại thời điểm này và hỗ trợ chính thức ngay cả từ 3.0 thậm chí còn nhanh chóng hết.

Đầu ra

Để dễ hình dung, đầu ra của tất cả các đường ống được chứng minh ở đây có dạng sau trước khi thực hiện "biến đổi" cuối cùng:

/* 1 */
{
    "_id" : null,
    "data" : [ 
        {
            "k" : "gender",
            "v" : [ 
                {
                    "name" : "Male",
                    "total" : 3.0
                }, 
                {
                    "name" : "Female",
                    "total" : 2.0
                }
            ]
        }, 
        {
            "k" : "education",
            "v" : [ 
                {
                    "name" : "M.C.A",
                    "total" : 1.0
                }, 
                {
                    "name" : "B.E",
                    "total" : 3.0
                }, 
                {
                    "name" : "B.Com",
                    "total" : 1.0
                }
            ]
        }, 
        {
            "k" : "caste",
            "v" : [ 
                {
                    "name" : "Lingayath",
                    "total" : 3.0
                }, 
                {
                    "name" : "Vokkaliga",
                    "total" : 2.0
                }
            ]
        }
    ]
}

Và sau đó bằng $ ReplaceRoot hoặc biến đổi con trỏ như được minh họa, kết quả trở thành:

/* 1 */
{
    "gender" : [ 
        {
            "name" : "Male",
            "total" : 3.0
        }, 
        {
            "name" : "Female",
            "total" : 2.0
        }
    ],
    "education" : [ 
        {
            "name" : "M.C.A",
            "total" : 1.0
        }, 
        {
            "name" : "B.E",
            "total" : 3.0
        }, 
        {
            "name" : "B.Com",
            "total" : 1.0
        }
    ],
    "caste" : [ 
        {
            "name" : "Lingayath",
            "total" : 3.0
        }, 
        {
            "name" : "Vokkaliga",
            "total" : 2.0
        }
    ]
}

Vì vậy, mặc dù chúng ta có thể đưa một số toán tử mới và ưa thích vào đường ống tổng hợp mà chúng ta có sẵn những toán tử đó, nhưng trường hợp sử dụng phổ biến nhất là trong "các phép biến đổi cuối đường ống", trong trường hợp đó, chúng ta cũng có thể thực hiện cùng một phép biến đổi trên mỗi tài liệu trong kết quả con trỏ trả về thay thế.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB - Thả một bộ sưu tập

  2. giới hạn số lượng bộ sưu tập trong cơ sở dữ liệu

  3. Chuyển đổi trường MongoDB từ Chuỗi thành ISODate trong mảng

  4. Không thể truy vấn mongoDB với mongoose trong node.js

  5. Các phương pháp hay nhất về NoSQL