Você está começando a pensar nas linhas certas aqui, pois estava indo na direção certa. Mudando sua mentalidade SQL, "distinto" é realmente apenas outra maneira de escrever um
$group
operação em qualquer idioma. Isso significa que você tem dois operações de grupo acontecendo aqui e, em termos de pipeline de agregação, dois estágios de pipeline. Apenas com documentos simplificados para visualizar:
{
"campaign_id": "A",
"campaign_name": "A",
"subscriber_id": "123"
},
{
"campaign_id": "A",
"campaign_name": "A",
"subscriber_id": "123"
},
{
"campaign_id": "A",
"campaign_name": "A",
"subscriber_id": "456"
}
É lógico que, para a combinação de "campanha" dada, a contagem total e a contagem "distinta" são "3" e "2", respectivamente. Portanto, a coisa lógica a fazer é "agrupar" todos esses valores "subscriber_id" primeiro e manter a contagem de ocorrências para cada um, depois enquanto pensa em "pipeline", "total" essas contagens por "campanha" e depois apenas conte o " ocorrências distintas" como um número separado:
db.campaigns.aggregate([
{ "$match": { "subscriber_id": { "$ne": null }}},
// Count all occurrences
{ "$group": {
"_id": {
"campaign_id": "$campaign_id",
"campaign_name": "$campaign_name",
"subscriber_id": "$subscriber_id"
},
"count": { "$sum": 1 }
}},
// Sum all occurrences and count distinct
{ "$group": {
"_id": {
"campaign_id": "$_id.campaign_id",
"campaign_name": "$_id.campaign_name"
},
"totalCount": { "$sum": "$count" },
"distinctCount": { "$sum": 1 }
}}
])
Após o primeiro "grupo" os documentos de saída podem ser visualizados assim:
{
"_id" : {
"campaign_id" : "A",
"campaign_name" : "A",
"subscriber_id" : "456"
},
"count" : 1
}
{
"_id" : {
"campaign_id" : "A",
"campaign_name" : "A",
"subscriber_id" : "123"
},
"count" : 2
}
Assim, dos "três" documentos da amostra, "2" pertence a um valor distinto e "1" a outro. Isso ainda pode ser totalizado com
$sum
para obter o total de documentos correspondentes que você faz na etapa seguinte, com o resultado final:{
"_id" : {
"campaign_id" : "A",
"campaign_name" : "A"
},
"totalCount" : 3,
"distinctCount" : 2
}
Uma analogia muito boa para o pipeline de agregação é o pipe unix "|" operador, que permite o "encadeamento" de operações para que você possa passar a saída de um comando para a entrada do próximo e assim por diante. Começar a pensar em seus requisitos de processamento dessa maneira ajudará você a entender melhor as operações com o pipeline de agregação.