web-dev-qa-db-fra.com

SQL comme GROUP BY AND HAVING

Je veux obtenir le nombre de groupes qui remplissent une certaine condition. En termes SQL, je veux faire ce qui suit dans Elasticsearch.

SELECT COUNT(*) FROM
(
   SELECT
    senderResellerId,
    SUM(requestAmountValue) AS t_amount
   FROM
    transactions
   GROUP BY
    senderResellerId
   HAVING
    t_amount > 10000 ) AS dum;

Jusqu'à présent, je pouvais regrouper par senderResellerId par agrégation de termes. Mais lorsque j'applique des filtres, cela ne fonctionne pas comme prévu.

Demande élastique

{
  "aggregations": {
    "reseller_sale_sum": {
      "aggs": {
        "sales": {
          "aggregations": {
            "reseller_sale": {
              "sum": {
                "field": "requestAmountValue"
              }
            }
          }, 
          "filter": {
            "range": {
              "reseller_sale": { 
                "gte": 10000
              }
            }
          }
        }
      }, 
      "terms": {
        "field": "senderResellerId", 
        "order": {
          "sales>reseller_sale": "desc"
        }, 
        "size": 5
      }
    }
  }, 
  "ext": {}, 
  "query": {  "match_all": {} }, 
  "size": 0
}

Réponse réelle

{
  "took" : 21,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "hits" : {
    "total" : 150824,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "reseller_sale_sum" : {
      "doc_count_error_upper_bound" : -1,
      "sum_other_doc_count" : 149609,
      "buckets" : [
        {
          "key" : "RES0000000004",
          "doc_count" : 8,
          "sales" : {
            "doc_count" : 0,
            "reseller_sale" : {
              "value" : 0.0
            }
          }
        },
        {
          "key" : "RES0000000005",
          "doc_count" : 39,
          "sales" : {
            "doc_count" : 0,
            "reseller_sale" : {
              "value" : 0.0
            }
          }
        },
        {
          "key" : "RES0000000006",
          "doc_count" : 57,
          "sales" : {
            "doc_count" : 0,
            "reseller_sale" : {
              "value" : 0.0
            }
          }
        },
        {
          "key" : "RES0000000007",
          "doc_count" : 134,
          "sales" : {
            "doc_count" : 0,
            "reseller_sale" : {
              "value" : 0.0
            }
          }
        }
          }
        }
      ]
    }
  }
}

Comme vous pouvez le voir dans la réponse ci-dessus, il renvoie des revendeurs mais l'agrégation reseller_sale est nulle dans les résultats.

Plus de détails sont ici .

13
chuckskull

Implémentation de AYANT - comportement similaire

Vous pouvez utiliser l'un des pipeline aggregations , à savoir agrégation du sélecteur de compartiment . La requête ressemblerait à ceci:

POST my_index/tdrs/_search
{
   "aggregations": {
      "reseller_sale_sum": {
         "aggregations": {
            "sales": {
               "sum": {
                  "field": "requestAmountValue"
               }
            },
            "max_sales": {
               "bucket_selector": {
                  "buckets_path": {
                     "var1": "sales"
                  },
                  "script": "params.var1 > 10000"
               }
            }
         },
         "terms": {
            "field": "senderResellerId",
            "order": {
               "sales": "desc"
            },
            "size": 5
         }
      }
   },
   "size": 0
}

Après avoir mis les documents suivants dans l'index:

  "hits": [
     {
        "_index": "my_index",
        "_type": "tdrs",
        "_id": "AV9Yh5F-dSw48Z0DWDys",
        "_score": 1,
        "_source": {
           "requestAmountValue": 7000,
           "senderResellerId": "ID_1"
        }
     },
     {
        "_index": "my_index",
        "_type": "tdrs",
        "_id": "AV9Yh684dSw48Z0DWDyt",
        "_score": 1,
        "_source": {
           "requestAmountValue": 5000,
           "senderResellerId": "ID_1"
        }
     },
     {
        "_index": "my_index",
        "_type": "tdrs",
        "_id": "AV9Yh8TBdSw48Z0DWDyu",
        "_score": 1,
        "_source": {
           "requestAmountValue": 1000,
           "senderResellerId": "ID_2"
        }
     }
  ]

Le résultat de la requête est:

"aggregations": {
      "reseller_sale_sum": {
         "doc_count_error_upper_bound": 0,
         "sum_other_doc_count": 0,
         "buckets": [
            {
               "key": "ID_1",
               "doc_count": 2,
               "sales": {
                  "value": 12000
               }
            }
         ]
      }
   }

C'est à dire. uniquement ceux senderResellerId dont les ventes cumulées sont >10000.

Compter les seaux

Pour implémenter un équivalent de SELECT COUNT(*) FROM (... HAVING) on peut utiliser une combinaison de agrégation de script de bucket avec agrégation de bucket sum . Bien qu'il ne semble y avoir aucun moyen direct de compter le nombre de compartiments que bucket_selector A réellement sélectionnés, nous pouvons définir un bucket_script Qui produit 0 Ou 1 Selon une condition, et sum_bucket qui produit son sum:

POST my_index/tdrs/_search
{
   "aggregations": {
      "reseller_sale_sum": {
         "aggregations": {
            "sales": {
               "sum": {
                  "field": "requestAmountValue"
               }
            },
            "max_sales": {
               "bucket_script": {
                  "buckets_path": {
                     "var1": "sales"
                  },
                  "script": "if (params.var1 > 10000) { 1 } else { 0 }"
               }
            }
         },
         "terms": {
            "field": "senderResellerId",
            "order": {
               "sales": "desc"
            }
         }
      },
      "max_sales_stats": {
         "sum_bucket": {
            "buckets_path": "reseller_sale_sum>max_sales"
         }
      }
   },
   "size": 0
}

La sortie sera:

   "aggregations": {
      "reseller_sale_sum": {
         "doc_count_error_upper_bound": 0,
         "sum_other_doc_count": 0,
         "buckets": [
            ...
         ]
      },
      "max_sales_stats": {
         "value": 1
      }
   }

Le nombre de compartiments souhaité se trouve dans max_sales_stats.value.

Considérations importantes

Je dois souligner 2 choses:

  1. La fonctionnalité est expérimentale (à partir d'ES 5.6, elle est toujours expérimentale, bien qu'elle ait été ajoutée dans 2.0.0-beta1 .)
  2. les agrégations de pipeline sont appliquées sur le résultat des agrégations précédentes:

Les agrégations de pipeline fonctionnent sur les sorties produites à partir d'autres agrégations plutôt qu'à partir de jeux de documents, ajoutant des informations à l'arborescence de sortie.

Cela signifie que l'agrégation bucket_selector Sera appliquée après et sur le résultat de l'agrégation terms sur senderResellerId. Par exemple, s'il existe plus de senderResellerId que size de terms définitions d'agrégation, vous n'obtiendrez pas tout les identifiants de la collection avec sum(sales) > 10000, mais uniquement ceux qui apparaissent dans la sortie de l'agrégation terms. Envisagez d'utiliser le tri et/ou définissez un paramètre size suffisant.

Cela s'applique également au deuxième cas, COUNT() (... HAVING), qui ne comptera que les compartiments réellement présents dans la sortie de l'agrégation.

Dans le cas où cette requête est trop lourde ou le nombre de compartiments trop important, pensez à dénormalisation vos données ou stockez cette somme directement dans le document, afin que vous puissiez utiliser ordinaire range requête pour atteindre votre objectif.

J'espère que cela pourra aider!

12
Nikolay Vasiliev