Programming

MongoDB에서 동등한 SQL 조인을 어떻게 수행합니까?

procodes 2020. 2. 15. 23:22
반응형

MongoDB에서 동등한 SQL 조인을 어떻게 수행합니까?


MongoDB에서 동등한 SQL 조인을 어떻게 수행합니까?

예를 들어 두 개의 모음 (사용자 및 의견)이 있고 각 사용자 정보와 함께 pid = 444로 모든 의견을 가져오고 싶습니다.

comments
  { uid:12345, pid:444, comment="blah" }
  { uid:12345, pid:888, comment="asdf" }
  { uid:99999, pid:444, comment="qwer" }

users
  { uid:12345, name:"john" }
  { uid:99999, name:"mia"  }

특정 필드 (예 : ... find ({pid : 444}))의 모든 댓글과 각 댓글과 관련된 사용자 정보를 한 번에 가져올 수있는 방법이 있습니까?

현재 우선 기준과 일치하는 주석을 얻은 다음 해당 결과 세트의 모든 uid를 파악하고 사용자 객체를 가져 와서 주석 결과와 병합합니다. 내가 잘못하고있는 것 같습니다.


Mongo 3.2 부터이 질문에 대한 답변은 더 이상 정확하지 않습니다. 집계 파이프 라인에 추가 된 새로운 $ lookup 연산자는 기본적으로 왼쪽 외부 조인과 동일합니다.

https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup

문서에서 :

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

물론 Mongo는 관계형 데이터베이스 아니며 개발자는 $ lookup에 대한 특정 사용 사례를 권장하기 위해 신중을 기울이고 있지만 현재 3.2 이상으로 MongoDB를 사용할 수 있습니다.


공식 mongodb 사이트 의이 페이지는 이 질문을 정확하게 해결 합니다 .

http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/

스토리 목록을 표시 할 때 스토리를 게시 한 사용자의 이름을 표시해야합니다. 관계형 데이터베이스를 사용하는 경우 사용자 및 저장소에 대한 조인을 수행하고 모든 개체를 단일 쿼리로 가져올 수 있습니다. 그러나 MongoDB는 조인을 지원하지 않으므로 때때로 약간의 비정규 화가 필요합니다. 여기서 이것은 'username'속성을 캐싱하는 것을 의미합니다.

우리가 어떤 보편적 인 법을 어 기고있는 것처럼 관계형 순수 주의자들은 이미 불안감을 느낄 수 있습니다. 그러나 MongoDB 콜렉션은 관계형 테이블과 동일하지 않다는 것을 명심하자. 각각 고유 한 디자인 목표를 제공합니다. 정규화 된 테이블은 원자 적으로 격리 된 데이터 청크를 제공합니다. 그러나 문서는 개체 전체를 더 자세히 나타냅니다. 소셜 뉴스 사이트의 경우 사용자 이름이 게시 된 기사에 내재되어 있다고 주장 할 수 있습니다.


mongodb 클라이언트 콘솔을 사용하여 몇 줄로 쉬운 기능으로 하나의 컬렉션에만있는 모든 데이터를 병합 / 결합 할 수 있으며 이제 원하는 쿼리를 수행 할 수 있습니다. 완전한 예에서

-저자 :

db.authors.insert([
    {
        _id: 'a1',
        name: { first: 'orlando', last: 'becerra' },
        age: 27
    },
    {
        _id: 'a2',
        name: { first: 'mayra', last: 'sanchez' },
        age: 21
    }
]);

.- 카테고리 :

db.categories.insert([
    {
        _id: 'c1',
        name: 'sci-fi'
    },
    {
        _id: 'c2',
        name: 'romance'
    }
]);

.- 책

db.books.insert([
    {
        _id: 'b1',
        name: 'Groovy Book',
        category: 'c1',
        authors: ['a1']
    },
    {
        _id: 'b2',
        name: 'Java Book',
        category: 'c2',
        authors: ['a1','a2']
    },
]);

.- 도서 대출

db.lendings.insert([
    {
        _id: 'l1',
        book: 'b1',
        date: new Date('01/01/11'),
        lendingBy: 'jose'
    },
    {
        _id: 'l2',
        book: 'b1',
        date: new Date('02/02/12'),
        lendingBy: 'maria'
    }
]);

.- 마술 :

db.books.find().forEach(
    function (newBook) {
        newBook.category = db.categories.findOne( { "_id": newBook.category } );
        newBook.lendings = db.lendings.find( { "book": newBook._id  } ).toArray();
        newBook.authors = db.authors.find( { "_id": { $in: newBook.authors }  } ).toArray();
        db.booksReloaded.insert(newBook);
    }
);

.- 새로운 수집 데이터를 얻으십시오 :

db.booksReloaded.find().pretty()

.- 응답 :)

{
    "_id" : "b1",
    "name" : "Groovy Book",
    "category" : {
        "_id" : "c1",
        "name" : "sci-fi"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        }
    ],
    "lendings" : [
        {
            "_id" : "l1",
            "book" : "b1",
            "date" : ISODate("2011-01-01T00:00:00Z"),
            "lendingBy" : "jose"
        },
        {
            "_id" : "l2",
            "book" : "b1",
            "date" : ISODate("2012-02-02T00:00:00Z"),
            "lendingBy" : "maria"
        }
    ]
}
{
    "_id" : "b2",
    "name" : "Java Book",
    "category" : {
        "_id" : "c2",
        "name" : "romance"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        },
        {
            "_id" : "a2",
            "name" : {
                "first" : "mayra",
                "last" : "sanchez"
            },
            "age" : 21
        }
    ],
    "lendings" : [ ]
}

이 줄이 당신을 도울 수 있기를 바랍니다.


설명 된대로해야합니다. MongoDB는 비 관계형 데이터베이스이며 조인을 지원하지 않습니다.


다음은 "join" 의 예입니다. * 배우영화 컬렉션 :

https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt

.mapReduce()방법 을 사용합니다

* join- 문서 지향 데이터베이스에 참여하는 대안


다른 사람들이 지적했듯이 실제로 원하지 않는 관계형 데이터베이스에서 관계형 데이터베이스를 만들려고 노력하고 있습니다. 어쨌든이 작업을 수행 해야하는 경우 사용할 수있는 솔루션입니다. 우리는 먼저 컬렉션 ​​A (또는 귀하의 경우 사용자)에서 foreach find를 수행 한 다음 각 항목을 객체로 얻은 다음 객체 속성 (케이스 uid)을 사용하여 두 번째 컬렉션 (케이스 주석)에서 조회합니다 그것을 찾은 다음 일치하는 것이 있고 인쇄하거나 무언가를 할 수 있습니다. 희망이 당신과 행운을 빕니다 :)

db.users.find().forEach(
function (object) {
    var commonInBoth=db.comments.findOne({ "uid": object.uid} );
    if (commonInBoth != null) {
        printjson(commonInBoth) ;
        printjson(object) ;
    }else {
        // did not match so we don't care in this case
    }
});

$ lookup , $ project$ match 의 올바른 조합 으로 여러 매개 변수에서 여러 테이블을 조인 할 수 있습니다. 여러 번 연결할 수 있기 때문입니다.

다음과 같이하고 싶다고 가정하자 ( reference )

SELECT S.* FROM LeftTable S
LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND 
S.MOB IS NOT NULL

1 단계 : 모든 테이블 연결

원하는만큼 테이블을 $ lookup 할 수 있습니다.

$ lookup- 쿼리에서 각 테이블 당 하나씩

$ unwind- 데이터가 올바르게 비정규 화되었으므로 배열로 래핑되기 때문에

파이썬 코드

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"}

                        ])

2 단계 : 모든 조건부 정의

$ project : 여기에 모든 조건문과 선택하려는 모든 변수를 정의하십시오.

파이썬 코드

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }}
                        ])

3 단계 : 모든 조건에 가입

$ match -OR 또는 AND 등을 사용하여 모든 조건을 조인합니다. 여러 조건이있을 수 있습니다.

$ project : 모든 조건을 정의하지 않음

파이썬 코드

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "$R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }},

                        # join all conditionals

                        {"$match": {
                          "$and": [
                            {"R.TIM": {"$gt": 0}}, 
                            {"MOB": {"$exists": True}},
                            {"midEq": {"$eq": True}}
                        ]}},

                        # undefine conditionals

                        {"$project": {
                          "midEq": 0
                        }}

                        ])

테이블, 조건부 및 조인의 거의 모든 조합이이 방식으로 수행 될 수 있습니다.


당신이하려는 일에 달려 있습니다.

현재 데이터베이스를 표준화 된 데이터베이스로 설정했으며, 이는 올바른 방식입니다.

그러나 다른 방법이 있습니다.

반복적으로 쿼리 할 수있는 사용자에 대한 참조가있는 각 게시물에 대한 주석이 포함 된 게시물 모음이있을 수 있습니다. 주석과 함께 사용자 이름을 저장할 수 있으며, 한 문서에 모두 저장할 수 있습니다.

NoSQL의 장점은 유연한 스키마와 매우 빠른 읽기 및 쓰기를 위해 설계되었습니다. 일반적인 빅 데이터 팜에서 데이터베이스는 가장 큰 병목 현상이며 응용 프로그램 및 프런트 엔드 서버보다 데이터베이스 엔진이 적습니다. 더 비싸지 만 더 강력하고 하드 드라이브 공간도 비교적 저렴합니다. 정규화는 공간 절약이라는 개념에서 비롯되지만 데이터베이스가 복잡한 조인을 수행하고 관계의 무결성을 확인하고 계단식 작업을 수행하는 비용이 발생합니다. 데이터베이스를 올바르게 설계하면 개발자가 두통을 덜 수 있습니다.

NoSQL을 사용하면 중복성 및 스토리지 공간이 비용 (업데이트를 수행하는 데 필요한 프로세서 시간과 추가 데이터를 저장하는 하드 드라이브 비용 모두)으로 인해 문제가되지 않는다는 것을 받아 들인 경우 비정규 화는 문제가되지 않습니다 (임베디드 어레이의 경우). 수십만 개의 항목이 성능 문제 일 수 있지만 대부분 문제가되지는 않습니다). 또한 모든 데이터베이스 클러스터에 대해 여러 응용 프로그램 및 프런트 엔드 서버가 있습니다. 그들이 조인을 많이 들어 올리고 데이터베이스 서버가 읽고 쓰는 것을 고수하게하십시오.

TL; DR : 당신이하고있는 일은 괜찮으며, 다른 방법도 있습니다. 훌륭한 예를 보려면 mongodb 문서의 데이터 모델 패턴을 확인하십시오. http://docs.mongodb.org/manual/data-modeling/


3.2 버전으로 제공되는 조회를 사용하여 Mongo에서 두 개의 콜렉션에 참여할 수 있습니다. 귀하의 경우 쿼리는

db.comments.aggregate({
    $lookup:{
        from:"users",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})

또는 사용자와 관련하여 참여할 수도 있습니다. 그러면 아래와 같이 약간 변경 될 수 있습니다.

db.users.aggregate({
    $lookup:{
        from:"comments",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})

SQL에서 왼쪽 및 오른쪽 조인과 동일하게 작동합니다.


많은 드라이버가 지원하는 사양 인 DBRef가 있습니다.

DBRef는 문서간에 참조를 작성하기위한보다 공식적인 사양입니다. DBRefs (일반적으로)는 컬렉션 이름과 개체 ID를 포함합니다. 대부분의 개발자는 컬렉션이 한 문서에서 다음 문서로 변경 될 수있는 경우에만 DBRefs를 사용합니다. 참조 된 컬렉션이 항상 동일 할 경우 위에서 설명한 수동 참조가 더 효율적입니다.

MongoDB 문서에서 가져온 것 : 데이터 모델> 데이터 모델 참조> 데이터베이스 참조


3.2.6 이전 에 Mongodb는 mysql처럼 join query를 지원하지 않습니다. 당신을 위해 작동하는 솔루션 아래.

 db.getCollection('comments').aggregate([
        {$match : {pid : 444}},
        {$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}},
   ])

$ 조회 (집계)

처리를 위해 "결합 된"콜렉션의 문서를 필터링하기 위해 동일한 데이터베이스의 비파괴 콜렉션에 왼쪽 외부 결합을 수행합니다. 각 입력 문서에 $ lookup 스테이지는 요소가 "결합 된"콜렉션의 일치하는 문서 인 새 배열 필드를 추가합니다. $ lookup 단계는 이러한 재구성 된 문서를 다음 단계로 전달합니다. $ lookup 단계에는 다음과 같은 구문이 있습니다.

평등 일치

입력 문서의 필드와 "결합 된"콜렉션의 문서의 필드 사이에 동등 일치를 수행하기 위해 $ lookup 스테이지에는 다음 구문이 있습니다.

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

작업은 다음 의사 SQL 문에 해당합니다.

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT <documents as determined from the pipeline>
                               FROM <collection to join>
                               WHERE <pipeline> );

몽고 URL


Postgres의 mongo_fdw 를 사용 하여 MongoDB에서 조인을 포함하여 SQL 쿼리를 실행할 수 있습니다 .


MongoDB는 조인을 허용하지 않지만 플러그인을 사용하여 처리 할 수 ​​있습니다. mongo-join 플러그인을 확인하십시오. 최고이며 이미 사용했습니다. 이처럼 npm을 사용하여 직접 설치할 수 있습니다 npm install mongo-join. 예제를 통해 전체 문서를 확인할 수 있습니다 .

(++) 컬렉션에 참여해야 할 때 정말 유용한 도구

(-) 쿼리의 최상위 수준에서만 조건을 적용 할 수 있습니다.

var Join = require('mongo-join').Join, mongodb = require('mongodb'), Db = mongodb.Db, Server = mongodb.Server;
db.open(function (err, Database) {
    Database.collection('Appoint', function (err, Appoints) {

        /* we can put conditions just on the top level */
        Appoints.find({_id_Doctor: id_doctor ,full_date :{ $gte: start_date },
            full_date :{ $lte: end_date }}, function (err, cursor) {
            var join = new Join(Database).on({
                field: '_id_Doctor', // <- field in Appoints document
                to: '_id',         // <- field in User doc. treated as ObjectID automatically.
                from: 'User'  // <- collection name for User doc
            }).on({
                field: '_id_Patient', // <- field in Appoints doc
                to: '_id',         // <- field in User doc. treated as ObjectID automatically.
                from: 'User'  // <- collection name for User doc
            })
            join.toArray(cursor, function (err, joinedDocs) {

                /* do what ever you want here */
                /* you can fetch the table and apply your own conditions */
                .....
                .....
                .....


                resp.status(200);
                resp.json({
                    "status": 200,
                    "message": "success",
                    "Appoints_Range": joinedDocs,


                });
                return resp;


            });

    });

집계 파이프 라인을 사용하여 수행 할 수 있지만 직접 작성하는 것은 쉽지 않습니다.

mongo-join-query쿼리에서 집계 파이프 라인을 자동으로 만드는 데 사용할 수 있습니다 .

쿼리는 다음과 같습니다.

const mongoose = require("mongoose");
const joinQuery = require("mongo-join-query");

joinQuery(
    mongoose.models.Comment,
    {
        find: { pid:444 },
        populate: ["uid"]
    },
    (err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);

결과는 uid필드에 사용자 개체가 있으며 원하는 수준으로 여러 수준을 연결할 수 있습니다. 사용자에 대한 참조를 채우면 팀을 참조하고 다른 것을 참조하는 등의 작업을 수행 할 수 있습니다.

면책 조항 : 나는 mongo-join-query이 정확한 문제를 해결하기 위해 썼다 .


playORM은 파티션 내에서 조인을 수행 할 수 있도록 파티션을 추가하는 S-SQL (Scalable SQL)을 사용하여이를 수행 할 수 있습니다.


아니, 당신이 잘못하고있는 것 같지 않습니다. MongoDB 조인은 "클라이언트 측"입니다. 당신이 말한 것처럼 :

현재 우선 기준과 일치하는 주석을 얻은 다음 해당 결과 세트의 모든 uid를 파악하고 사용자 객체를 가져 와서 주석 결과와 병합합니다. 내가 잘못하고있는 것 같습니다.

1) Select from the collection you're interested in.
2) From that collection pull out ID's you need
3) Select from other collections
4) Decorate your original results.

"실제"조인은 아니지만 "많은"면 조인에 대한 중복 행을 처리 할 필요가 없기 때문에 원래 선택된 세트를 장식하기 때문에 실제로 SQL 조인보다 훨씬 유용합니다.

이 페이지에는 넌센스와 FUD가 많이 있습니다. 5 년이 지난 후에도 MongoDB는 여전히 문제입니다.


정규화 된 데이터 테이블이 필요한 경우 다른 데이터베이스 솔루션을 시도해야한다고 생각합니다.

그러나 나는 Git 에서 MOngo에 대한 해결책을 코드로 삽입했다-영화의 이름은 있지만 noi 영화의 ID는있다 .

문제

당신이 한 영화의 배열을 가진 액터 컬렉션이 있습니다.

각각의 액터 배열로 영화 컬렉션을 생성하려고합니다.

일부 샘플 데이터

 db.actors.insert( { actor: "Richard Gere", movies: ['Pretty Woman', 'Runaway Bride', 'Chicago'] });
 db.actors.insert( { actor: "Julia Roberts", movies: ['Pretty Woman', 'Runaway Bride', 'Erin Brockovich'] });

해결책

액터 문서에서 각 무비를 반복하고 각 무비를 개별적으로 방출해야합니다.

여기서 캐치는 축소 단계에 있습니다. Reduce 단계에서 배열을 방출 할 수 없으므로 반환 된 "value"문서 내에 Actors 배열을 작성해야합니다.

코드
map = function() {
  for(var i in this.movies){
    key = { movie: this.movies[i] };
    value = { actors: [ this.actor ] };
    emit(key, value);
  }
}

reduce = function(key, values) {
  actor_list = { actors: [] };
  for(var i in values) {
    actor_list.actors = values[i].actors.concat(actor_list.actors);
  }
  return actor_list;
}

actor_list가 실제로 배열을 포함하는 자바 스크립트 객체 인 방법에 주목하십시오. 또한 map은 동일한 구조를 나타냅니다.

다음을 실행하여 맵 / 축소를 실행하고 "피벗"콜렉션으로 출력 한 후 결과를 인쇄하십시오.

printjson (db.actors.mapReduce (지도, 축소, "피벗")); db.pivot.find (). forEach (printjson);

다음은 샘플 출력입니다. "Pretty Woman"과 "Runaway Bride"에는 "Richard Gere"와 "Julia Roberts"가 있습니다.

{ "_id" : { "movie" : "Chicago" }, "value" : { "actors" : [ "Richard Gere" ] } }
{ "_id" : { "movie" : "Erin Brockovich" }, "value" : { "actors" : [ "Julia Roberts" ] } }
{ "_id" : { "movie" : "Pretty Woman" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } }
{ "_id" : { "movie" : "Runaway Bride" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } }


mongoDB 하위 쿼리를 사용하여 두 컬렉션을 병합 할 수 있습니다. 예를 들면 다음과 같습니다.

`db.commentss.insert([
  { uid:12345, pid:444, comment:"blah" },
  { uid:12345, pid:888, comment:"asdf" },
  { uid:99999, pid:444, comment:"qwer" }])`

사용자-

db.userss.insert([
  { uid:12345, name:"john" },
  { uid:99999, name:"mia"  }])

JOIN에 대한 MongoDB 하위 쿼리

`db.commentss.find().forEach(
    function (newComments) {
        newComments.userss = db.userss.find( { "uid": newComments.uid } ).toArray();
        db.newCommentUsers.insert(newComments);
    }
);`

새로 생성 된 컬렉션에서 결과 가져 오기

db.newCommentUsers.find().pretty()

결과--

`{
    "_id" : ObjectId("5511236e29709afa03f226ef"),
    "uid" : 12345,
    "pid" : 444,
    "comment" : "blah",
    "userss" : [
        {
            "_id" : ObjectId("5511238129709afa03f226f2"),
            "uid" : 12345,
            "name" : "john"
        }
    ]
}
{
    "_id" : ObjectId("5511236e29709afa03f226f0"),
    "uid" : 12345,
    "pid" : 888,
    "comment" : "asdf",
    "userss" : [
        {
            "_id" : ObjectId("5511238129709afa03f226f2"),
            "uid" : 12345,
            "name" : "john"
        }
    ]
}
{
    "_id" : ObjectId("5511236e29709afa03f226f1"),
    "uid" : 99999,
    "pid" : 444,
    "comment" : "qwer",
    "userss" : [
        {
            "_id" : ObjectId("5511238129709afa03f226f3"),
            "uid" : 99999,
            "name" : "mia"
        }
    ]
}`

희망이 도움이 될 것입니다.

참고 : https://stackoverflow.com/questions/2350495/how-do-i-perform-the-sql-join-equivalent-in-mongodb



반응형