Utilisation de l'API Java Elasticsearch (agrégation)

Cet article est une suite de Utilisation de l'API Java Elasticsearch (enregistrement BulkRequest). Voir ci-dessus pour les exigences, la structure des données, etc.

D'ailleurs, je souhaite traiter diverses données enregistrées en Bulk.

Exigences

――Je souhaite afficher les 10 meilleurs classements quotidiens des ventes. ――Je souhaite pouvoir changer la plage de dates par jour, semaine, mois, etc. ――Je souhaite pouvoir modifier le nombre de classements des ventes. ――Je veux pouvoir le voir séparément par général et par genre.

Méthode d'acquisition

Kibana

image.png

Vous pouvez maintenant consulter le TOP 5 d'un jour particulier.

Requete

La configuration ci-dessus peut être exprimée comme une requête comme suit.

GET sample-ranking-qiita-*/_search
{
  "aggs": {
    "range": {
      "date_range": {
        "field": "execDate",
        "ranges": [
          {
            "from": "2018/01/15T00:00:00+09:00",
            "to": "2018/01/15T23:59:59+09:00"
          }
        ]
      },
      "aggs": {
        "title_rate": {
          "terms": {
            "field": "title.keyword",
            "size": 5,
            "order": {
              "sum_rate": "desc"
            }
          },
          "aggs": {
            "sum_rate": {
              "sum": {
                "field": "rate"
              }
            }
          }
        }
      }
    }
  }
}

N'est-il pas un peu difficile de voir la nidification des aggs? Les trois agrégats suivants sont définis.

  1. Additionnez les «taux» avec le nom «sum_rate»
  2. Affichez sum_rate par title dans l'ordre décroissant avec le nom title_rate
  3. La plage d'agrégation de "title_rate" doit être comprise entre "from" et "to" de la "date d'exec".

SQL

Pour référence, c'est l'image de SQL. (Je ne l'ai pas essayé, donc je suis désolé si je fais une erreur)

ranking.sql


SELECT
	ranking
	, title
	, sum_rate
FROM (
	select
		ROW_NUMBER() OVER (ORDER BY sum_rate DESC) AS ranking
		, tb1.title
		, tb1.sum_rate 
	FROM (
		SELECT
			title
			, SUM(rate) as sum_rate 
		FROM
			tbl_ranking
		WHERE
			execDate >= '2018/01/15T00:00:00+09:00'
			AND execDate <= '2018/01/15T23:59:59+09:00'
		GROUP BY
			title
	) tb1
)
WHERE
	ranking <= 5

Résultat d'acquisition

Lorsque j'obtiens le résultat de la requête, le résultat de aggs est imbriqué dans la balise ʻaggregations. La liste dans la balise buckets` est la liste de résultats pour chaque agrégation. Les mêmes titres affichés dans Kibana sont alignés. La valeur totale du taux correspond également.

{
  "took": 426,
  "timed_out": false,
  "_shards": {
    "total": 15,
    "successful": 15,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 24800,
    "max_score": 1,
    "hits": [
(Omis)
    ]
  },
  "aggregations": {
    "range": {
      "buckets": [
        {
          // 3. title_Plage de dates agrégée du taux------------------------------
          "key": "2018/01/14T15:00:00+0000-2018/01/15T14:59:59+0000",
          "from": 1515942000000,
          "from_as_string": "2018/01/14T15:00:00+0000",
          "to": 1516028399000,
          "to_as_string": "2018/01/15T14:59:59+0000",
          "doc_count": 400,
          // 2.titre une autre somme_ordre décroissant des taux------------------------------
          "title_rate": {
            "doc_count_error_upper_bound": -1,
            "sum_other_doc_count": 380,
            "buckets": [
              {
                "key": "Nouvelle ouverture",
                "doc_count": 4,
                "sum_rate": {
                  // 1.taux total------------
                  "value": 355
                }
              },
              {
                "key": "Lycéen",
                "doc_count": 4,
                "sum_rate": {
                  "value": 337
                }
              },
              {
                "key": "Test intermédiaire",
                "doc_count": 4,
                "sum_rate": {
                  "value": 333
                }
              },
              {
                "key": "La vérité de l'hôte",
                "doc_count": 4,
                "sum_rate": {
                  "value": 292
                }
              },
              {
                "key": "Un moment du match",
                "doc_count": 4,
                "sum_rate": {
                  "value": 292
                }
              }
            ]
          }
        }
      ]
    }
  }
}

Java-API

Maintenant, obtenons la requête ci-dessus avec Java-API.

AggsSampleViewer.java


public class AggsSampleViewer {

	private static Logger logger = LoggerFactory.getLogger(AggsSampleViewer.class);

	//Format d'index année / mois(yyyyMM)
	private static DateTimeFormatter YM_INDEX_FORMATTER;
	//Format de la date d'exécution(yyyy/MM/dd'T'HH:mm:ss+09:00)
	private static DateTimeFormatter DT_INDEX_FORMATTER;

	//Diverses informations de réglage
	QiitaSettingBean setting;


	/**
	 *constructeur
	 * @paramètres de réglage informations de réglage
	 */
	public AggsSampleViewer(QiitaSettingBean setting) {
		super();
		this.setting = setting;

		DT_INDEX_FORMATTER = DateTimeFormatter.ofPattern(setting.getElasticearch().getExecDateFormat());
		YM_INDEX_FORMATTER = DateTimeFormatter.ofPattern(setting.getElasticearch().getIndexYmFormat());
	}

	/**
	 *Exécution du processus d'acquisition
	 *
	 * @param subIndex Nom du sous-index(Nul au moment de l'acquisition totale)
	 * @throws Exception
	 */
	public void execute(String subIndex) throws Exception {

        //Créer un client pour gérer ELS en Java
        // setting.getElasticearch().getAddress():adresse IP
        // setting.getElasticearch().getPort():numéro de port(Généralement 9300)
		TransportClient client = new PreBuiltTransportClient(Settings.EMPTY).addTransportAddress(
				new InetSocketTransportAddress(InetAddress.getByName(setting.getElasticearch().getAddress()),
						setting.getElasticearch().getPort()));

		//Génération d'index pour la recherche(Lorsque le sous-index est spécifié, le sous est également inclus)
		String searchIndex = setting.getElsImport().getIndex();
		if (StringUtils.isNotEmpty(subIndex)) {
			searchIndex = searchIndex + "-" + subIndex;
		}
		searchIndex += "*";

		logger.debug("■■■ Index cible de la recherche: " + searchIndex);

		// sum_Aggs for rate: Calculez la valeur totale du taux
		AggregationBuilder sumRateAggs = AggregationBuilders.sum("sum_rate").field("rate");

		// title_Aggs for rate: Ordre décroissant du taux par titre
		AggregationBuilder titleRateAggs = AggregationBuilders.terms("title_rate")
				//Agréger par titre
				.field("title.keyword")
				// setting.getRankingLimit():Numéro de classement
				.size(setting.getRankingLimit())
				//L'ordre d'affichage est la somme_ordre décroissant de taux
				.order(Order.aggregation("sum_rate", false))
				//aux subaggs, sum_Ajout d'Aggs pour le taux
				.subAggregation(sumRateAggs)
				;

		//Aggs pour la plage de dates
		AggregationBuilder dateRangeAggs = AggregationBuilders.dateRange("range")
				//La cible de la plage de dates est la date d'exécution du traitement
				.field("execDate")
				//Définissez la plage de dates du jour avant 0h00 le jour cible de traitement à 0h00 le jour
				//Exemple) setting.getNow(): 2018/01/16 13:20:35
				//		from:2018/01/15 00:00:00
				//		to  :2018/01/16 00:00:00
				.addRange(
						setting.getNow().minusDays(1).truncatedTo(ChronoUnit.DAYS).format(DT_INDEX_FORMATTER)
						, setting.getNow().truncatedTo(ChronoUnit.DAYS).format(DT_INDEX_FORMATTER)
				)
				//subaggs, titre_Ajout d'Aggs pour le taux
				.subAggregation(titleRateAggs)
				;

		//Requête entière
		SearchRequestBuilder requestBuilder = client.prepareSearch(searchIndex)
				.addAggregation(dateRangeAggs);


		//Obtenez les résultats de la recherche
		SearchResponse res = requestBuilder.get();

		//Obtenez le résultat de Aggs de la requête entière
		for (Aggregation aggs : res.getAggregations()) {
			logger.debug("aggs name: "+ aggs.getName());
		}

		//[Aggs pour la plage de dates](type:date_range) ----------------
		InternalDateRange resDateRangeAggs = res.getAggregations().get("range");
		//Vérifiez le compartiment dans Aggs pour la plage de dates
		for (InternalDateRange.Bucket dateRangeBucket : resDateRangeAggs.getBuckets()) {
			//clé et doc_le nombre peut être obtenu avec n'importe quel Bucket
			logger.debug("* resDateRangeAggs bucket key: "+ dateRangeBucket.getKey());
			logger.debug("* resDateRangeAggs bucket doc_count: "+ dateRangeBucket.getDocCount());
			//De et À peuvent être obtenus car ils font référence au compartiment pour la plage de dates.
			logger.debug("* resDateRangeAggs bucket from: "+ dateRangeBucket.getFromAsString());
			logger.debug("* resDateRangeAggs bucket to: "+ dateRangeBucket.getToAsString());

			//Vérifier les résultats Aggs dans Aggs pour la plage de dates
			for (Aggregation aggs : dateRangeBucket.getAggregations()) {
				logger.debug("* resDateRangeAggs bucket aggs: "+ aggs.getName());
			}

			// 【title_Aggs pour taux](type:terms) ----------------
			Terms resTitleRateAggs = dateRangeBucket.getAggregations().get("title_rate");

			// title_Vérifiez le seau en Aggs pour le taux
			for (Terms.Bucket termBucket : resTitleRateAggs.getBuckets()) {
				logger.debug("** resTitleRateAggs bucket key: "+ termBucket.getKey());
				logger.debug("** resTitleRateAggs bucket doc_count: "+ termBucket.getDocCount());

				// title_Confirmation du résultat Aggs pour le taux
				for (Aggregation aggs : termBucket.getAggregations()) {
					logger.debug("** resTitleRateAggs bucket aggs: "+ aggs.getName());
				}

				// 【sum_Aggs pour taux](type:sum) ----------------
				Sum resSumRateAggs = termBucket.getAggregations().get("sum_rate");

				//Il n'y a pas d'Aggs dans Sum et vous pouvez obtenir des résultats de somme
				logger.debug("*** resSumRateAggs sum name: "+ resSumRateAggs.getName());
				logger.debug("*** resSumRateAggs sum value: "+ resSumRateAggs.getValueAsString());
			}
		}
	}
}

Nous définirons Aggs avec la même structure imbriquée que la requête. Est-il un peu difficile de savoir quel Aggs utiliser et quel Bucket est retourné ^^; Il est préférable de vérifier la formule dans ce domaine.

Comment définir la formule est écrite, mais quel type d'information sera retourné en conséquence n'est pas écrit ... Est-ce que cela va être considéré en utilisant pleinement la complétion automatique etc ...?

https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-aggs.html

L'exécution se fait comme ça.

main.java


AggsSampleViewer viewer = new AggsSampleViewer(setting);

//Génération d'informations de classement global
logger.debug("■ Classement général----------------------------------------");
viewer.execute(null);

//Génération d'informations de classement Shonen
logger.debug("■ Classement des garçons----------------------------------------");
viewer.execute("boy");

Résultat d'exécution

■ Classement général---------------------------------------- 
■■■ Index cible de la recherche: sample-ranking-qiita* 
aggs name: range 
* resDateRangeAggs bucket key: 2018/01/14T15:00:00+0000-2018/01/15T15:00:00+0000 
* resDateRangeAggs bucket doc_count: 400 
* resDateRangeAggs bucket from: 2018/01/14T15:00:00+0000 
* resDateRangeAggs bucket to: 2018/01/15T15:00:00+0000 
* resDateRangeAggs bucket aggs: title_rate 
** resTitleRateAggs bucket key:Nouvelle ouverture
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 355.0 
** resTitleRateAggs bucket key:Lycéen
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 337.0 
** resTitleRateAggs bucket key:Test intermédiaire
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 333.0 
** resTitleRateAggs bucket key:La vérité de l'hôte
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 292.0 
** resTitleRateAggs bucket key:Un moment du match
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 292.0 
■ Classement des garçons---------------------------------------- 
■■■ Index cible de la recherche: sample-ranking-qiita-boy* 
aggs name: range 
* resDateRangeAggs bucket key: 2018/01/14T15:00:00+0000-2018/01/15T15:00:00+0000 
* resDateRangeAggs bucket doc_count: 141 
* resDateRangeAggs bucket from: 2018/01/14T15:00:00+0000 
* resDateRangeAggs bucket to: 2018/01/15T15:00:00+0000 
* resDateRangeAggs bucket aggs: title_rate 
** resTitleRateAggs bucket key:Lycéen
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 337.0 
** resTitleRateAggs bucket key:Knockout et épisode 2
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 287.0 
** resTitleRateAggs bucket key:Résultats en avant et apparence
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 278.0 
** resTitleRateAggs bucket key:cœur
** resTitleRateAggs bucket doc_count: 4 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 267.0 
** resTitleRateAggs bucket key:Championnat
** resTitleRateAggs bucket doc_count: 3 
** resTitleRateAggs bucket aggs: sum_rate 
*** resSumRateAggs sum name: sum_rate 
*** resSumRateAggs sum value: 260.0 

J'ai pu obtenir les mêmes résultats que Kibana et la requête! Ah, c'est naturel comme requête d'agrégation, mais avec ʻaggs, seuls les champs utilisés pour l'agrégation peuvent être acquis, et d'autres champs (auteurs, etc. dans ce cas) ne peuvent pas être acquis. Ce serait bien si je pouvais obtenir le résultat de l'association avec le champ dans ʻaggs d'un seul coup avec query, mais cela ne semble pas être le cas. Si vous augmentez la «taille» de la «requête», la possibilité de l'obtenir augmentera, mais je pense que la limite supérieure de la «taille» de la «requête» est «10000», donc vous pouvez certainement l'obtenir dans cette plage. Si vous n'êtes pas sûr, il vaut mieux l'obtenir tranquillement avec une autre requête.

Le genre de livre est défini comme un sous-index pour faciliter la réduction de la portée de la recherche. Je me suis demandé si la réduction de l'indice réduirait au maximum le fardeau lors de la recherche d'une fourchette à long terme. Bien entendu, il peut être inclus dans la condition de requête.

AggsSampleViewer2.java


		//Requête pour le filtrage par genre
		QueryBuilder queryBuilder = QueryBuilders.termQuery("index", subIndex);


		//Requête entière
		SearchRequestBuilder requestBuilder = client.prepareSearch(searchIndex)
				.addAggregation(dateRangeAggs);

		//Ajouter une requête si un sous-index est spécifié
		if (StringUtils.isNotEmpty(subIndex)) {
			requestBuilder.setQuery(queryBuilder);
		}

Vous pouvez également affiner en utilisant Query Builder comme ceci.

Changement de plage de dates

Si vous souhaitez modifier la plage de dates, par exemple hebdomadaire ou mensuelle, basculez dans ʻaddRange de dateRangeAggs`. J'ai également créé des informations de définition de plage car je souhaite pouvoir les modifier librement à partir des paramètres.

AggsSampleViewer3.java


		//Aggs pour la plage de dates
		AggregationBuilder dateRangeAggs = AggregationBuilders.dateRange("range")
				//La cible de la plage de dates est la date d'exécution du traitement
				.field("execDate")
				//Définir la plage de dates entre De et A de la plage de recherche
				//Exemple)De 10 jours avant la date d'exécution à minuit le jour d'exécution
				//"searchRange": {
				//		"from": "-10",
				//		"fromChrono": "days",
				//		"to": "0",
				//		"toChrono": "days"
				//}
				.addRange(
						DT_INDEX_FORMATTER.format(
								//Du paramètre de plage de recherche à la date d'exécution est ajouté en unités d'unités spécifiées.
								setting.getNow()
									.plus(setting.getSearchRange().getFrom()
											, setting.getSearchRange().findFromChronoUnit())
								//Coupé après la journée (faites-le exactement à 0 heure)
								.truncatedTo(ChronoUnit.DAYS))
						, DT_INDEX_FORMATTER.format(
								//Ajouter le À du paramètre de plage de recherche à la date d'exécution dans l'unité d'unité spécifiée
								setting.getNow()
									.plus(setting.getSearchRange().getTo()
											, setting.getSearchRange().findToChronoUnit())
								//Coupé après la journée (faites-le exactement à 0 heure)
								.truncatedTo(ChronoUnit.DAYS))
				)
				//subaggs, titre_Ajout d'Aggs pour le taux
				.subAggregation(titleRateAggs)
				;

RangeBean.java


public class RangeBean {
	private int from;
	private String fromChrono;
	private int to;
	private String toChrono;

	public ChronoUnit findFromChronoUnit() {
		return findChronoUnit(fromChrono);
	}

	public ChronoUnit findToChronoUnit() {
		return findChronoUnit(toChrono);
	}

	/**
	 *Changer la chaîne Chrono en unité
	 *
	 * @param chrono
	 * @return
	 */
	public ChronoUnit findChronoUnit(String chrono) {
		if (StringUtils.equals(chrono, "days")) {
			return ChronoUnit.DAYS;
		}
		if (StringUtils.equals(chrono, "weeks")) {
			return ChronoUnit.WEEKS;
		}
		if (StringUtils.equals(chrono, "months")) {
			return ChronoUnit.MONTHS;
		}
		if (StringUtils.equals(chrono, "years")) {
			return ChronoUnit.YEARS;
		}

		return null;
	}

	//(Omis)
}

Vous pouvez désormais spécifier la plage à votre guise, mensuellement ou sur trois ans.

La prochaine fois, j'aimerais écrire comment spécifier et obtenir une requête avec Java-API.

Recommended Posts

Utilisation de l'API Java Elasticsearch (agrégation)
Utiliser l'API Java Elasticsearch (enregistrement BulkRequest) [Notes supplémentaires]
J'ai essayé d'utiliser l'API Elasticsearch en Java
Surveillance de l'état des applications Java à l'aide d'Elasticsearch