For now the only solution I have is with query duplication:

(
  (sum(kafka_consumergroup_lag[1d]) by (consumergroup) >= 100)
  * on(consumergroup) group_left(team)
  catalog_entities_info
)
or ignoring(team) (sum(kafka_consumergroup_lag[1d]) by (consumergroup) >= 100)

First half will add team label where possible, and second or half will add missed data (important to merge vectors while ignoring added team label, otherwise we will have duplicates).

Answer from aiven on Stack Overflow
🌐
Prometheus
prometheus.io › docs › prometheus › latest › querying › operators
Operators | Prometheus
Many-to-one and one-to-many matchings refer to the case where each vector element on the "one"-side can match with multiple elements on the "many"-side. This has to be explicitly requested using the group_left or group_right modifiers, where left/right determines which vector has the higher cardinality.
🌐
Robust Perception
robustperception.io › using-group_left-to-calculate-label-proportions
Using group_left to calculate label proportions – Robust Perception | Prometheus Monitoring Experts
When you've broken a metric out into labels a common need is to tell what proportion each label represents of the total. The group_left modifier of Prometheus is the key.
Discussions

promql - join prometheus queries while keeping data from the left side - Stack Overflow
I want to build promql query that joins two vectors: one with some metrics and other is informational. The caveat is that info vector doesn't have all the information for "joining label",... More on stackoverflow.com
🌐 stackoverflow.com
monitoring - How can I 'join' two metrics in a Prometheus query? - Stack Overflow
iximiuz.com/en/posts/prometheus-vector-matching ... Save this answer. ... Show activity on this post. You can use the argument list of group_left to include extra labels from the right operand (parentheses and indents for clarity): More on stackoverflow.com
🌐 stackoverflow.com
Error when using group_left in Prometheus - Stack Overflow
The group_left() modifier expects that the right-hand side of * operator (and any other operator) contains only a single time series per each label=value set specified inside on() modifier. Otherwise it returns duplicate series for the match group error. More on stackoverflow.com
🌐 stackoverflow.com
Prometheus add extra label on join request without using group statement? - Stack Overflow
If you need retaining only some labels from the left side, then just drop unneeded labels after the calculations of q1 * on (...) group_left() q2. Prometheus doesn't provide functions, which can drop unneeded labels from time series (or leave only the needed labels), but this functionality ... More on stackoverflow.com
🌐 stackoverflow.com
People also ask

What is the difference between group_left and group_right in Prometheus?
group_left is used when the left side of a vector match has more time series than the right. group_right is for the opposite case—when the right side has more time series. These are used to join metrics that have different cardinalities, such as pairing metrics with metadata.
🌐
last9.io
last9.io › blog › prometheus-group-by-label
Prometheus Group By Label: Advanced Aggregation Techniques for ...
How can I group labels in a Prometheus query?
Use by (label1, label2) to retain specific labels or without (label) to exclude one or more. For example: sum(cpu_usage) by (region) sum(memory_usage) without (instance)
🌐
last9.io
last9.io › blog › prometheus-group-by-label
Prometheus Group By Label: Advanced Aggregation Techniques for ...
What is the job label in Prometheus?
The job label identifies the target being scraped. It’s automatically assigned by Prometheus and helps group metrics by application, service, or source as defined in the scrape configuration.
🌐
last9.io
last9.io › blog › prometheus-group-by-label
Prometheus Group By Label: Advanced Aggregation Techniques for ...
🌐
Webscale
webscale.com › home › blog › prometheus querying – breaking down promql
PromQL Querying: group_left, Joins, and Working Examples
June 8, 2026 - varnish_main_client_req{namespace="section-9469f9cc28d8d"} * on (pod) group_left(node) kube_pod_info ... We can then take this over to Grafana to make a dashboard and chart, add this data to a graph panel, and clearly view it all. Sometimes graphing a query might overload the server or browser, or lead to a time out because the amount of data is too large. When constructing queries over unknown data, it is better to begin building the query in the tabular view of Prometheus’ expression browser until you arrive at a reasonable result set (i.e.
🌐
Chris's Wiki
utcc.utoronto.ca › ~cks › space › blog › sysadmin › PrometheusGroupLeftAndRightNotes
Prometheus's group_left() and group_right() operators
December 17, 2023 - With group_left() this is the right side, and with group_right() it's the left side. In theory this sounds symmetric, but in practice it's not, because if you're forced to use group_right(), by itself your alert labels won't come from the metric whose value generated the alert.
🌐
Grafana
grafana.com › blog › promql-vector-matching-what-it-is-and-how-it-affects-your-prometheus-queries
PromQL vector matching: what it is and how it affects your Prometheus queries | Grafana Labs
December 14, 2024 - When we want to preserve labels from the left side, we use group_left, and when from the right side, we use group_right: Now, we have all our Pokémon type percentages. To check, the water type percentage is 5.26%, just as we calculated before.
🌐
Iximiuz
iximiuz.com › en › posts › prometheus-vector-matching
Prometheus Cheat Sheet - How to Join Multiple Metrics (Vector Matching)
November 30, 2021 - Interesting, that even if the "one" side doesn't have collisions and group_left or group_right is specified, a query can still fail with: multiple matches for labels: grouping labels must ensure unique matches · It can happen because, for every element on the "many" side, Prometheus should find no more than one element from the "one" side.
Find elsewhere
Top answer
1 of 3
76

You can use the argument list of group_left to include extra labels from the right operand (parentheses and indents for clarity):

(
  max(consul_health_service_status{status="critical"}) 
  by (service_name,status,node) == 1
)
   + on(service_name,node) group_left(env)
(
   0 * consul_service_tags
)

The important part here is the operation + on(service_name,node) group_left(env):

  • the + is "abused" as a join operator (fine since 0 * consul_service_tags always has the value 0)
  • group_left(env) is the modifier that includes the extra label env from the right (consul_service_tags)
2 of 3
15

It is a good practice in Prometheus ecosystem to expose additional labels, which can be joined to multiple metrics, via a separate info-like metric as explained in this article. For example, consul_service_tags metric exposes a set of tags, which can be joined to metrics via (service_name, node) labels.

The join is usually performed via on() and group_left() modifiers applied to * operation. The * doesn't modify values for time series on the left side because info-like metrics usually have constant 1 values. The on() modifier is used for limiting the labels used for finding matching time series on the left and the right side of *. The group_left() modifier is used for adding additional labels from time series on the right side of *. See these docs for details.

For example, the following PromQL query adds env label from consul_service_tags metric to consul_health_service_status metric with the same set of (service_name, node) labels:

consul_health_service_status
  * on(service_name, node) group_left(env)
consul_service_tags

Additional label filters can be added to consul_health_service_status if needed. For example, the following query returns only time series with status="critical" label:

consul_health_service_status{status="critical"}
  * on(service_name, node) group_left(env)
consul_service_tags
🌐
Last9
last9.io › blog › prometheus-group-by-label
Prometheus Group By Label: Advanced Aggregation Techniques for Monitoring | Last9
June 12, 2026 - group_left is used when the left side of a vector match has more time series than the right. group_right is for the opposite case—when the right side has more time series. These are used to join metrics that have different cardinalities, such ...
Top answer
1 of 2
6

The RHS has no instance label, so it's trying to match all those series to one on the LHS. Try max by (node, instance) (kube_node_labels{label_grid="true"})

2 of 2
2

The group_left() modifier expects that the right-hand side of * operator (and any other operator) contains only a single time series per each label=value set specified inside on() modifier. Otherwise it returns duplicate series for the match group error. See these docs for more details.

The solution is to specify the proper labels inside on() modifier, so every label=value set for these labels would have only a single time series on the right-hand side of * operator. The instance label is a good candidate to put inside on() modifier. The only issue is that the dcgm_gpu_utilization and kube_node_labels are collected from different targets with different TCP port numbers. So they have different instance label values (see these docs explaining how instance label is generated). This breaks matching rules for * operator, so the following query returns nothing:

floor(avg_over_time(dcgm_gpu_utilization{cluster_name="researchers"}[5m]))
  * on (instance) group_left(node)
kube_node_labels{label_grid="true"}

This can be fixed by stripping the port number from instance label at both sides of * operator with the help of label_replace function:

label_replace(
  floor(avg_over_time(dcgm_gpu_utilization{cluster_name="researchers"}[5m])),
  "hostname",
  "$1",
  "instance",
  "([^:]+):.+"
)
  * on (hostname) group_left(node)
label_replace(
  kube_node_labels{label_grid="true"},
  "hostname",
  "$1",
  "instance",
  "([^:]+):.+"
)

This query extracts hostname part from instance labels, puts it into a hostname label and then joins the left-hand side and the right-hand side time series on this label.

🌐
Promlabs
promlabs.com › promql-cheat-sheet
PromLabs | PromQL Cheat Sheet
Include any label sets that are either on the left or right side: up{job="prometheus"} or up{job="node"} Open in PromLens · Include any label sets that are present both on the left and right side: node_network_mtu_bytes and (node_network_address_assign_type == 0) Open in PromLens ·
Top answer
1 of 1
1

It is OK to use group_left() for 1:1 query if you need retaining all the labels from time series on the left side (or from the right side if group_right() is used). This doesn't introduce performance penalty.

If you put a list of labels inside group_left(), then these labels will be copied from time series on the right side additionally to all the labels from time series on the left side of the specified binary operator.

See more details in the official docs.

If you need retaining only some labels from the left side, then just drop unneeded labels after the calculations of q1 * on (...) group_left() q2. Prometheus doesn't provide functions, which can drop unneeded labels from time series (or leave only the needed labels), but this functionality can be emulated with sum(...) without (...) or sum(...) by (...) in most cases. For example, the following query leaves only (namespace, instance_id, label2) labels from time series matching metric1{namespace="ns1"} in the result:

sum(
  (metric1{namespace="ns1"} == 1)
    * on (namespace, instance_id) group_left()
  (metric2{namespace="ns1", label3="something"} == 1)
) by (namespace, instance_id, label2)

Note that the sum() may return unexpected results if multiple time series have the same set of output labels specified inside by (...).

P.S. Take a look also at label_del and label_keep functions provided by VictoriaMetrics - the Prometheus-like monitoring solution I work on. These functions are easier to use instead of sum(...) by (...) or sum(...) without (...) trick when some labels need to be dropped / left in the result.

🌐
Coralogix
coralogix.com › home › promql tutorial: 5 tricks to become a prometheus god
PromQL Tutorial: 5 Tricks to Become a Prometheus God
June 3, 2025 - Conversely you can use ignoring to specify which label you don’t want to join on. For example the query a * ignoring (baz) group_left(baz) b joins a and b on every label except baz. Let’s assume a contains labels foo and bar and b contains foo, bar and baz.
🌐
Google Groups
groups.google.com › g › prometheus-users › c › cGycJ1CPedc
can we combine different metrics
4. if there are N metrics on the LHS which match one metric on the RHS, or vice versa, then you can specify "group left" or "group right" 5. you can't have a situation where N metrics on the LHS match M metrics on the RHS (N>1 and M>1) ... https://prometheus.io/docs/prometheus/latest/query...
🌐
SigNoz
signoz.io › guides › how can i 'join' two metrics in a prometheus query? - joining metrics in promql
How can I 'join' two metrics in a Prometheus query? - Joining Metrics in PromQL | SigNoz
July 24, 2024 - avg(http_request_duration_seconds) by (service) / on(instance) group_left avg(rate(node_cpu_seconds_total{mode!="idle"}[5m])) by (instance) Joining metrics in Prometheus enables complex and insightful queries
🌐
Google Groups
groups.google.com › g › prometheus-users › c › KmPqvJuFoHY
turning a group_left use of info-block into a range vector
device_boot_time * on (instance) group_left(region, firmware) device_info{region="California", firmware="v1.0"} Even through seems to return an instant value - I have not been able to figure out if it is possible to turn into a range vector · What you want to do is the range vector operation, and then the group_left:
🌐
Grafana
grafana.com › blog › 2021 › 08 › 04 › how-to-use-promql-joins-for-more-effective-queries-of-prometheus-metrics-at-scale
How to use PromQL joins for more effective queries of Prometheus metrics at scale | Grafana Labs
August 5, 2021 - groups: - name: slo_metric expr: count by (reference_label) ((api_response_latency * on (labelone,labeltwo) group_left(reference_label) sli_info > 100) This would work for every metric with those label names and associated mappings. Voila! You get the same result as the existing rule group, without the 18,000 individual rules. Tags · PrometheusPromQL ·