MongoDB SQL Union All ($facet) 하는 방법

2021. 4. 11. 22:50mongodb

반응형

오늘은 과거에 은근 찾기 어려웠던, 그 명령어 union에 대해 알아보겠다.

 

왜 찾기 어려웠냐면... mongodb에서도 비슷한 명령어가 존재한다..

 

당시 내가 하고 싶은 명령어는 sql로 단순했다.

 

[SQL]
select *

 from plc 

where time > '2020-03-01 00:00:00'

limit 1

 

union all

 

select *

 from plc 

where '2020-03-01 01:00:00'<= time < '2020-03-01 04:00:00' 

 

union all

 

select *

 from plc 

where time > '2020-03-01 04:00:00'

limit 1

 

딱 봐도 쉬운 쿼리 아닌가,,

 

과거에는 해당 명령어를 조회할 줄 몰라서...

 

그냥 python에서 두번 조회해서 합쳤다..

 

성능 저하를 알면서도..당시에 프로젝트 기간내에 하기 위해,,

 

급하게 했다..지금도 그 코드가 활용되고 잘 돌아가고 있다고 해서...

 

해당 방법을 해당 회사에게 전달해 주었으나..

 

잘 돌아가는 운영을 바꾸지 않았다고 한다.

 

잘 돌아간다고 문제가 없는건 아니다..

 

Python으로 3개의 collection에서 데이터를 조회해서 union을 하면,

 

일단 성능이 저하가 발생한다. 

 

최소 mongodb에 3번을 왔다갔다 해야한다.

 

정말 마이그레이션을 주 업무로 하는 나로써 제일 혐오하는 작업이다.

 

성능, 부하, 자원 심지어 프로그램까지 어떤거 하나 단순하게 없는 최악의 작업이였다.

 

하지만, 프로젝트가 끝나고, 시간이 남았을 때, 해당 쿼리를 찾아봤다.

 

생각보다 쉬웠으나, 크게 두가지 문제가 있다.

 

1. sql를 아는 사람에겐 생각보다 이해하기 까다롭다..

 

( 나 역시 sql에서 nosql 로 전환한 사람으로써, 쿼리 기반 사람에겐 이해가 어렵다. )

 

2. 데이터 사이즈가 너무 크면, out of memory가 나온다는 단점이 있다.

 

( index와 상관없이, 그냥 반환하는 size가 너무 크면 나타나는 거 같다. )

 

( 그래도 충분한 데이터 사이즈를 넘길수 있으니, 시도를 해보는 걸 추천한다. )

 

그럼 해당 방법을 알아 보겠다.

 

mongodb에서 union 명령어는 $facet 이다.

 

그럼 위 쿼리를 mongodb 쿼리로 바꿔보겠다. 

 

[nosql]

db.getCollection('test_data').aggregate([ { 

    $facet : {

        'frist_data' : [{$match : {tag_id : 'zone_1', time : {$lt : '2020-03-01 01:00:00'}} }, {$sort : {time : 1} }, {$limit : 1} ], 

        'center_data' : [{$match : {tag_id : 'zone_1', time : {$gte : '2020-03-01 01:00:00', $lt : '2020-03-01 04:00:00' }} } ],

        'last_data' : [{$match : {tag_id : 'zone_1', time : {$lt : '2020-03-01 04:00:00'}} }, {$sort : {time : -1} },{$limit : 1} ], 

        } 

    } ])

 

지금보면 역시나, 쿼리는 심플하다.

 

왜케 당시엔 조회해도 안나왔는지...모르겠다..

 

설명을 하자면, aggregate를 사용해서, $facet 함수에 위에서 union한 select 문장 3개를 적어준다. 

 

sql과 차이가 있다면, union 대상마다 frist_data, center_data, last_data 이런식으로 명칭을 달아준다. 

(frist_data, center_data, last_data는 이름을 바꿀수 있다. a,b,c 이런식도 가능하다 )

 

그리고 각각의 명칭마다 데이터를 조회하는 조건을 넣어주면,

 

해당 데이터는 반환이 된다. 

 

반환 데이터의 format은 json로 반환이 되며, 아래와 같다.

 

[result]
{

    "frist_data" : [ 

        {

            "_id" : ObjectId("5f110141daa5a27441192445"),

            "tag_id" : "zone_1",

            "val" : 100,

            "time" : "2020-03-01 00:00:00"

        }

    ],

    "center_data" : [ 

        {

            "_id" : ObjectId("5f110194fa5f2321214a1ecb"),

            "tag_id" : "zone_1",

            "val" : 200,

            "time" : "2020-03-01 01:00:00"

        }, 

        {

            "_id" : ObjectId("5f1101a6fa5f2321214a1ed4"),

            "tag_id" : "zone_1",

            "val" : 300,

            "time" : "2020-03-01 02:00:00"

        }, 

        {

            "_id" : ObjectId("5f1101bafa5f2321214a1edb"),

            "tag_id" : "zone_1",

            "val" : 400,

            "time" : "2020-03-01 03:00:00"

        }

    ],

    "last_data" : [ 

        {

            "_id" : ObjectId("5f1101bafa5f2321214a1edb"),

            "tag_id" : "zone_1",

            "val" : 400,

            "time" : "2020-03-01 03:00:00"

        }

    ]

}

 

$facet에서 선언한 3개의 명칭 frist_data, center_data, last_data 를 josn key로 생성되고, 

 

해당 key 밑에 josn list로 데이터가 들어간다. 

 

list 형태여서 out of memory가 나오는게 아닌지 생각이 든다. 

 

충분히 대용량의 데이터가 출력이 되어서, 큰게 불만은 없이 사용한 쿼리이다. 

 

혹시 나와 같은 문제를 생각하고 있는 사람이 있다면,

 

해당 샘플을 참고해 보는걸 추천해본다~!!

 

 

 

 

 

 

 

 

반응형