Repository: apache/skywalking (language: python)

Issue:
[Feature] Support isEmptyValue flag for metrics query
### Search before asking

- [X] I had searched in the [issues](https://github.com/apache/skywalking/issues?q=is%3Aissue) and found no similar feature requirement.


### Description

In the current case, we always respond to zero if timestamp slots are absent.

I opened https://github.com/apache/skywalking-query-protocol/pull/110 and https://github.com/apache/skywalking-query-protocol/pull/112 for a protocol update(9.5.0 only), and we could distinguish these two cases. 

### Use case

`no value`(no metric at this time bucket) V.S. `actual zero value`(such as success rate == 0% or the value is less than 1 due to precision)

@wankai123 According to this, let's evaluate whether the default value still makes sense, or we should remove that permanently. 

FYI @Fine0830 After this lands on the backend, UI should do
1. Build a flat line cross the absent(isEmptyValue = true) data point. Rather than dropping the line to zero as a peak. 
2. Skip absent(isEmptyValue = true) data point when the table runs preview avg.

### Related issues

_No response_

### Are you willing to submit a PR?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://www.apache.org/foundation/policies/conduct)



Discussion / hints:
@toffentoffen This is what I talked today.
@Fine0830 With another https://github.com/apache/skywalking-query-protocol/pull/112, query single value should switch this new way and support empty value to show `--` or `no data` on the widget.

Fix patch:
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index b3c3052a6fa4..3b3f036f0d7a 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -22,6 +22,8 @@
 * Fix possible NPE when initialize `IntList`.
 * Support parse PromQL expression has empty labels in the braces for metadata query.
 * Support alarm metric OP `!=`.
+* Support metrics query indicates whether value == 0 represents actually zero or no data.
+* Fix `NPE` when query the not exist series indexes in ElasticSearch storage. 
 
 #### UI
 * Revert: cpm5d function. This feature is cancelled from backend.
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsQueryService.java
index fb6b73cee7b5..e84698d88f89 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsQueryService.java
@@ -25,6 +25,7 @@
 import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
 import org.apache.skywalking.oap.server.core.query.type.HeatMap;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.query.type.NullableValue;
 import org.apache.skywalking.oap.server.core.storage.StorageModule;
 import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
@@ -50,7 +51,7 @@ private IMetricsQueryDAO getMetricQueryDAO() {
     /**
      * Read metrics single value in the duration of required metrics
      */
-    public long readMetricsValue(MetricsCondition condition, Duration duration) throws IOException {
+    public NullableValue readMetricsValue(MetricsCondition condition, Duration duration) throws IOException {
         return getMetricQueryDAO().readMetricsValue(
             condition, ValueColumnMetadata.INSTANCE.getValueCName(condition.getName()), duration);
     }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
index 85531062c708..84b3fb0c79f5 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
@@ -19,6 +19,7 @@
 package org.apache.skywalking.oap.server.core.query.type;
 
 import io.vavr.collection.Stream;
+import io.vavr.control.Option;
 import java.util.ArrayList;
 import java.util.List;
 import lombok.Getter;
@@ -31,16 +32,25 @@ public void addKVInt(KVInt e) {
         values.add(e);
     }
 
-    public long findValue(String id, int defaultValue) {
+    /**
+     * Return defaultValue if absent.
+     */
+    public KVInt findValue(String id, int defaultValue) {
         for (KVInt value : values) {
             if (value.getId().equals(id)) {
-                return value.getValue();
+                return value;
             }
         }
-        return defaultValue;
+
+        return new KVInt(id, defaultValue, true);
     }
 
-    public long latestValue(int defaultValue) {
-        return Stream.ofAll(values).map(KVInt::getValue).findLast(v -> v != defaultValue).getOrElse((long) defaultValue);
+    public NullableValue latestValue(int defaultValue) {
+        Option<KVInt> kvInt = Stream.ofAll(values).findLast(v -> !v.isEmptyValue());
+        if (kvInt.isEmpty()) {
+            return new NullableValue(defaultValue, true);
+        } else {
+            return new NullableValue(kvInt.get().getValue(), kvInt.get().isEmptyValue());
+        }
     }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/KVInt.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/KVInt.java
index cbe6b2c5e160..973cf1734713 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/KVInt.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/KVInt.java
@@ -27,4 +27,13 @@ public class KVInt {
     private String id;
     private long value;
     private boolean isEmptyValue;
+
+    public KVInt(String id, long value, boolean isEmptyValue) {
+        this.id = id;
+        this.value = value;
+        this.isEmptyValue = isEmptyValue;
+    }
+
+    public KVInt() {
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/NullableValue.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/NullableValue.java
index 8462ea693c30..23f9894a0960 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/NullableValue.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/NullableValue.java
@@ -24,4 +24,12 @@
 public class NullableValue {
     private long value;
     private boolean isEmptyValue;
+
+    public NullableValue() {
+    }
+
+    public NullableValue(long value, boolean isEmptyValue) {
+        this.value = value;
+        this.isEmptyValue = isEmptyValue;
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
index 548d5c1b169a..b09726276a55 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
@@ -24,7 +24,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
 import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
@@ -34,6 +33,7 @@
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.query.type.NullableValue;
 import org.apache.skywalking.oap.server.core.storage.DAO;
 import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 
@@ -45,7 +45,7 @@
  * @since 8.0.0
  */
 public interface IMetricsQueryDAO extends DAO {
-    long readMetricsValue(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException;
+    NullableValue readMetricsValue(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException;
 
     MetricsValues readMetricsValues(MetricsCondition condition,
                                     String valueColumnName,
@@ -66,10 +66,7 @@ public static IntValues sortValues(IntValues origin, List<String> expectedOrder,
             IntValues intValues = new IntValues();
 
             expectedOrder.forEach(id -> {
-                KVInt e = new KVInt();
-                e.setId(id);
-                e.setValue(origin.findValue(id, defaultValue));
-                intValues.addKVInt(e);
+                intValues.addKVInt(origin.findValue(id, defaultValue));
             });
 
             return intValues;
@@ -93,7 +90,6 @@ public static List<MetricsValues> sortValues(List<MetricsValues> origin,
          */
         public static List<MetricsValues> composeLabelValue(final MetricsCondition condition,
             final List<String> labels,
-            final List<String> ids,
             final Map<String, DataTable> idMap) {
             List<String> allLabels;
             if (Objects.isNull(labels) || labels.size() < 1 || labels.stream().allMatch(Strings::isNullOrEmpty)) {
@@ -105,12 +101,16 @@ public static List<MetricsValues> composeLabelValue(final MetricsCondition condi
             }
             final int defaultValue = ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName());
             List<LabeledValue> labeledValues = new TreeSet<>(allLabels).stream()
-                .flatMap(label -> ids.stream().map(id ->
+                .flatMap(label -> idMap.entrySet().stream().map(kv -> Objects.nonNull(kv.getValue().get(label)) ?
                     new LabeledValue(
                         label,
-                        id,
-                        Optional.ofNullable(idMap.getOrDefault(id, new DataTable()).get(label)).orElse((long) defaultValue))))
-                .collect(toList());
+                        kv.getKey(),
+                        kv.getValue().get(label), false) :
+                    new LabeledValue(
+                        label,
+                        kv.getKey(),
+                        defaultValue, true)
+                )).collect(toList());
             MetricsValues current = new MetricsValues();
             List<MetricsValues> result = new ArrayList<>();
             for (LabeledValue each : labeledValues) {
@@ -131,12 +131,9 @@ class LabeledValue {
         private final String label;
         private final KVInt kv;
 
-        public LabeledValue(String label, String id, long value) {
+        public LabeledValue(String label, String id, long value, boolean isEmptyValue) {
             this.label = label;
-            KVInt kv = new KVInt();
-            kv.setId(id);
-            kv.setValue(value);
-            this.kv = kv;
+            this.kv = new KVInt(id, value, isEmptyValue);
         }
     }
 }
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
index 06977eed16c4..d4b6f9cc34f4 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
@@ -35,7 +35,6 @@
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
-import org.apache.skywalking.oap.server.core.query.type.NullableValue;
 import org.apache.skywalking.oap.server.core.query.type.Thermodynamic;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 
@@ -93,14 +92,6 @@ public IntValues getLinearIntValues(final MetricCondition metrics,
         return metricsValues.getValues();
     }
 
-    public NullableValue readNullableMetricsValue(MetricsCondition condition, Duration duration) {
-        // TODO default implantation
-        final NullableValue nullableValue = new NullableValue();
-        nullableValue.setValue(0);
-        nullableValue.setEmptyValue(true);
-        return nullableValue;
-    }
-
     public List<IntValues> getMultipleLinearIntValues(final MetricCondition metrics, final int numOfLinear,
                                                       final Duration duration) throws IOException {
         MetricsCondition condition = new MetricsCondition();
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
index c7da4da067e9..9bd7b0356838 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
@@ -42,6 +42,7 @@
 import org.apache.skywalking.oap.server.core.query.type.HeatMap;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.query.type.NullableValue;
 import org.apache.skywalking.oap.server.core.query.type.Record;
 import org.apache.skywalking.oap.server.core.query.type.SelectedRecord;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
@@ -122,6 +123,13 @@ public long readMetricsValue(MetricsCondition condition, Duration duration) thro
         if (!condition.senseScope() || !condition.getEntity().isValid()) {
             return 0;
         }
+        return getMetricsQueryService().readMetricsValue(condition, duration).getValue();
+    }
+
+    public NullableValue readNullableMetricsValue(MetricsCondition condition, Duration duration) throws IOException {
+        if (!condition.senseScope() || !condition.getEntity().isValid()) {
+            return new NullableValue(0, true);
+        }
         return getMetricsQueryService().readMetricsValue(condition, duration);
     }
 
@@ -139,6 +147,7 @@ public MetricsValues readMetricsValues(MetricsCondition condition, Duration dura
                 final KVInt kvInt = new KVInt();
                 kvInt.setId(id);
                 kvInt.setValue(0);
+                kvInt.setEmptyValue(true);
                 values.getValues().addKVInt(kvInt);
             });
             return values;
@@ -177,6 +186,7 @@ public List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
                     final KVInt kvInt = new KVInt();
                     kvInt.setId(id);
                     kvInt.setValue(0);
+                    kvInt.setEmptyValue(true);
                     values.getValues().addKVInt(kvInt);
                 });
                 values.setLabel(label);
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java
index 047f89a9c9ea..2d2edc7d3e5c 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMetricsQueryDAO.java
@@ -44,6 +44,7 @@
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.query.type.NullableValue;
 import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
 import org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageClient;
@@ -58,7 +59,7 @@ public BanyanDBMetricsQueryDAO(BanyanDBStorageClient client) {
     }
 
     @Override
-    public long readMetricsValue(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException {
+    public NullableValue readMetricsValue(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException {
         String modelName = condition.getName();
         MetadataRegistry.Schema schema = MetadataRegistry.INSTANCE.findMetadata(modelName, duration.getStep());
         if (schema == null) {
@@ -88,9 +89,9 @@ protected void apply(MeasureQuery query) {
                 });
 
         for (DataPoint dataPoint : resp.getDataPoints()) {
-            return ((Number) dataPoint.getFieldValue(valueColumnName)).longValue();
+            return new NullableValue(((Number) dataPoint.getFieldValue(valueColumnName)).longValue(), false);
         }
-        return defaultValue;
+        return new NullableValue(defaultValue, true);
     }
 
     private void buildAggregationQuery(MeasureQuery query, String valueColumnName, Function function) {
@@ -125,12 +126,12 @@ public MetricsValues readMetricsValues(MetricsCondition condition, String valueC
             String id = ts.id(entityID);
             KVInt kvInt = new KVInt();
             kvInt.setId(id);
-            kvInt.setValue(0);
             if (idMap.containsKey(ts.getPoint())) {
                 DataPoint dataPoint = idMap.get(ts.getPoint());
                 kvInt.setValue(extractFieldValue(schema, valueColumnName, dataPoint));
             } else {
                 kvInt.setValue(ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName()));
+                kvInt.setEmptyValue(true);
             }
             intValues.addKVInt(kvInt);
         }
@@ -171,7 +172,7 @@ public List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
         }
 
         return Util.sortValues(
-                Util.composeLabelValue(condition, labels, ids, dataTableMap),
+                Util.composeLabelValue(condition, labels, dataTableMap),
                 ids,
                 ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName())
         );
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/AggregationQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/AggregationQueryEsDAO.java
index 3fd19217ed00..6d881829f742 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/AggregationQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/AggregationQueryEsDAO.java
@@ -18,6 +18,7 @@
 
 package org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query;
 
+import java.util.Objects;
 import org.apache.skywalking.library.elasticsearch.requests.search.BoolQueryBuilder;
 import org.apache.skywalking.library.elasticsearch.requests.search.Query;
 import org.apache.skywalking.library.elasticsearch.requests.search.RangeQueryBuilder;
@@ -117,18 +118,19 @@ public List<SelectedRecord> sortMetrics(final TopNCondition condition,
             duration.getEndTimeBucketInSec()), search.build());
 
         final List<SelectedRecord> topNList = new ArrayList<>();
-        final Map<String, Object> idTerms =
-            (Map<String, Object>) response.getAggregations().get(Metrics.ENTITY_ID);
-        final List<Map<String, Object>> buckets =
-            (List<Map<String, Object>>) idTerms.get("buckets");
-        for (Map<String, Object> termsBucket : buckets) {
-            SelectedRecord record = new SelectedRecord();
-            record.setId((String) termsBucket.get("key"));
-            Map<String, Object> value = (Map<String, Object>) termsBucket.get(realValueColumn);
-            record.setValue(String.valueOf(((Number) value.get("value")).longValue()));
-            topNList.add(record);
+        if (Objects.nonNull(response.getAggregations())) {
+            final Map<String, Object> idTerms =
+                (Map<String, Object>) response.getAggregations().get(Metrics.ENTITY_ID);
+            final List<Map<String, Object>> buckets =
+                (List<Map<String, Object>>) idTerms.get("buckets");
+            for (Map<String, Object> termsBucket : buckets) {
+                SelectedRecord record = new SelectedRecord();
+                record.setId((String) termsBucket.get("key"));
+                Map<String, Object> value = (Map<String, Object>) termsBucket.get(realValueColumn);
+                record.setValue(String.valueOf(((Number) value.get("value")).longValue()));
+                topNList.add(record);
+            }
         }
-
         return topNList;
     }
 }
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
index 9796a149d7f4..160bb78f34f7 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
@@ -22,6 +22,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.skywalking.library.elasticsearch.requests.search.Query;
@@ -45,6 +46,7 @@
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.query.type.NullableValue;
 import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
 import org.apache.skywalking.oap.server.library.client.elasticsearch.ElasticSearchClient;
@@ -60,9 +62,9 @@ public MetricsQueryEsDAO(ElasticSearchClient client) {
     }
 
     @Override
-    public long readMetricsValue(final MetricsCondition condition,
-                                 final String valueColumnName,
-                                 final Duration duration) {
+    public NullableValue readMetricsValue(final MetricsCondition condition,
+                                          final String valueColumnName,
+                                          final Duration duration) {
         final String realValueColumn = IndexController.LogicIndicesRegister.getPhysicalColumnName(condition.getName(), valueColumnName);
         final SearchBuilder sourceBuilder = buildQuery(condition, duration);
         int defaultValue = ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName());
@@ -87,16 +89,18 @@ public long readMetricsValue(final MetricsCondition condition,
             duration.getStartTimeBucketInSec(),
             duration.getEndTimeBucketInSec()), sourceBuilder.build());
 
-        final Map<String, Object> idTerms =
-            (Map<String, Object>) response.getAggregations().get(Metrics.ENTITY_ID);
-        final List<Map<String, Object>> buckets =
-            (List<Map<String, Object>>) idTerms.get("buckets");
+        if (Objects.nonNull(response.getAggregations())) {
+            final Map<String, Object> idTerms =
+                (Map<String, Object>) response.getAggregations().get(Metrics.ENTITY_ID);
+            final List<Map<String, Object>> buckets =
+                (List<Map<String, Object>>) idTerms.get("buckets");
 
-        for (Map<String, Object> idBucket : buckets) {
-            final Map<String, Object> agg = (Map<String, Object>) idBucket.get(realValueColumn);
-            return ((Number) agg.get("value")).longValue();
+            for (Map<String, Object> idBucket : buckets) {
+                final Map<String, Object> agg = (Map<String, Object>) idBucket.get(realValueColumn);
+                return new NullableValue(((Number) agg.get("value")).longValue(), false);
+            }
         }
-        return defaultValue;
+        return new NullableValue(defaultValue, true);
     }
 
     @Override
@@ -130,14 +134,16 @@ public MetricsValues readMetricsValues(final MetricsCondition condition,
             for (String id : ids) {
                 KVInt kvInt = new KVInt();
                 kvInt.setId(id);
-                kvInt.setValue(0);
                 if (idMap.containsKey(id)) {
                     Map<String, Object> source = idMap.get(id);
-                    kvInt.setValue(((Number) source.getOrDefault(realValueColumn, 0)).longValue());
-                } else {
-                    kvInt.setValue(ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName()));
+                    if (source.get(realValueColumn) != null) {
+                        kvInt.setValue(((Number) source.get(realValueColumn)).longValue());
+                    } else {
+                        kvInt.setValue(ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName()));
+                        kvInt.setEmptyValue(true);
+                    }
+                    intValues.addKVInt(kvInt);
                 }
-                intValues.addKVInt(kvInt);
             }
         }
         metricsValues.setValues(
@@ -177,14 +183,16 @@ public List<MetricsValues> readLabeledMetricsValues(final MetricsCondition condi
 
         if (response.isPresent()) {
             for (final Document document : response.get()) {
-                idMap.put(
-                    document.getId(),
-                    new DataTable((String) document.getSource().getOrDefault(realValueColumn, ""))
-                );
+                if (document.getSource().get(realValueColumn) != null) {
+                    idMap.put(
+                        document.getId(),
+                        new DataTable((String) document.getSource().get(realValueColumn))
+                    );
+                }
             }
         }
         return Util.sortValues(
-            Util.composeLabelValue(condition, labels, ids, idMap),
+            Util.composeLabelValue(condition, labels, idMap),
             ids,
             ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName())
         );
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/TagAutoCompleteQueryDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/TagAutoCompleteQueryDAO.java
index dc062d5ccba5..3ea88b1a4e8a 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/TagAutoCompleteQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/TagAutoCompleteQueryDAO.java
@@ -22,6 +22,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import org.apache.skywalking.library.elasticsearch.requests.search.BoolQueryBuilder;
 import org.apache.skywalking.library.elasticsearch.requests.search.Query;
@@ -77,16 +78,19 @@ public Set<String> queryTagAutocompleteKeys(final TagType tagType,
             ),
             search.build()
         );
-        Map<String, Object> terms =
-            (Map<String, Object>) response.getAggregations().get(TagAutocompleteData.TAG_KEY);
-        List<Map<String, Object>> buckets = (List<Map<String, Object>>) terms.get("buckets");
         Set<String> tagKeys = new HashSet<>();
-        for (Map<String, Object> bucket : buckets) {
-            String tagKey = (String) bucket.get("key");
-            if (StringUtil.isEmpty(tagKey)) {
-                continue;
+        if (Objects.nonNull(response.getAggregations())) {
+            Map<String, Object> terms =
+                (Map<String, Object>) response.getAggregations().get(TagAutocompleteData.TAG_KEY);
+            List<Map<String, Object>> buckets = (List<Map<String, Object>>) terms.get("buckets");
+
+            for (Map<String, Object> bucket : buckets) {
+                String tagKey = (String) bucket.get("key");
+                if (StringUtil.isEmpty(tagKey)) {
+                    continue;
+                }
+                tagKeys.add(tagKey);
             }
-            tagKeys.add(tagKey);
         }
         return tagKeys;
     }
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/zipkin/ZipkinQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/zipkin/ZipkinQueryEsDAO.java
index f7734f0d8d0d..8a69573abe52 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/zipkin/ZipkinQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/zipkin/ZipkinQueryEsDAO.java
@@ -23,6 +23,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import org.apache.skywalking.library.elasticsearch.requests.search.BoolQueryBuilder;
 import org.apache.skywalking.library.elasticsearch.requests.search.Query;
@@ -219,16 +220,17 @@ public List<List<Span>> getTraces(final QueryRequest request, Duration duration)
             TimeBucket.getRecordTimeBucket(startTimeMillis),
             TimeBucket.getRecordTimeBucket(endTimeMillis)
         ), search.build());
-        final Map<String, Object> idTerms =
-            (Map<String, Object>) traceIdResponse.getAggregations().get(ZipkinSpanRecord.TRACE_ID);
-        final List<Map<String, Object>> buckets =
-            (List<Map<String, Object>>) idTerms.get("buckets");
-
         Set<String> traceIds = new HashSet<>();
-        for (Map<String, Object> idBucket : buckets) {
-            traceIds.add((String) idBucket.get("key"));
-        }
+        if (Objects.nonNull(traceIdResponse.getAggregations())) {
+            final Map<String, Object> idTerms =
+                (Map<String, Object>) traceIdResponse.getAggregations().get(ZipkinSpanRecord.TRACE_ID);
+            final List<Map<String, Object>> buckets =
+                (List<Map<String, Object>>) idTerms.get("buckets");
 
+            for (Map<String, Object> idBucket : buckets) {
+                traceIds.add((String) idBucket.get("key"));
+            }
+        }
         return getTraces(traceIds);
     }
 
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java
index 19edf4bb2d03..b58f05374bf0 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCMetricsQueryDAO.java
@@ -28,6 +28,7 @@
 import org.apache.skywalking.oap.server.core.query.type.HeatMap;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.query.type.NullableValue;
 import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
 import org.apache.skywalking.oap.server.library.client.jdbc.hikaricp.JDBCClient;
@@ -46,9 +47,9 @@ public class JDBCMetricsQueryDAO extends JDBCSQLExecutor implements IMetricsQuer
 
     @Override
     @SneakyThrows
-    public long readMetricsValue(final MetricsCondition condition,
-                                String valueColumnName,
-                                final Duration duration) {
+    public NullableValue readMetricsValue(final MetricsCondition condition,
+                                          String valueColumnName,
+                                          final Duration duration) {
         final var tables = tableHelper.getTablesForRead(
             condition.getName(),
             duration.getStartTimeBucket(),
@@ -91,21 +92,25 @@ public long readMetricsValue(final MetricsCondition condition,
             parameters.add(condition.getName());
             sql.append(" group by " + Metrics.ENTITY_ID);
 
-            results.add(jdbcClient.executeQuery(
+            jdbcClient.executeQuery(
                 sql.toString(),
                 resultSet -> {
                     if (resultSet.next()) {
-                        return resultSet.getLong("result");
+                        results.add(resultSet.getLong("result"));
                     }
-                    return (long) defaultValue;
+                    return null;
                 },
                 parameters.toArray(new Object[0])
-            ));
+            );
+        }
+
+        if (results.size() == 0) {
+            return new NullableValue(defaultValue, true);
         }
         if (op.equals("avg")) {
-            return results.stream().mapToLong(it -> it).sum() / results.size();
+            return new NullableValue(results.stream().mapToLong(it -> it).sum() / results.size(), false);
         }
-        return results.stream().mapToLong(it -> it).sum();
+        return new NullableValue(results.stream().mapToLong(it -> it).sum(), false);
     }
 
     protected StringBuilder buildMetricsValueSql(String op, String valueColumnName, String conditionName) {
@@ -215,7 +220,7 @@ public List<MetricsValues> readLabeledMetricsValues(final MetricsCondition condi
         }
 
         return Util.sortValues(
-            Util.composeLabelValue(condition, labels, ids, idMap),
+            Util.composeLabelValue(condition, labels, idMap),
             ids,
             ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName())
         );


Test patch:
diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/query/MetricsQueryUtilTest.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/query/MetricsQueryUtilTest.java
index 00ea8c0979f2..51d6267be34f 100644
--- a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/query/MetricsQueryUtilTest.java
+++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/query/MetricsQueryUtilTest.java
@@ -81,14 +81,14 @@ public static Collection<Object[]> data() {
                 of("202007291425", new DataTable("200,1|400,2"), "202007291426", new DataTable("200,3|400,8")),
                 "[{\"label\":\"200\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":1,\"isEmptyValue\":false},{\"id\":\"202007291426\",\"value\":3,\"isEmptyValue\":false}]}}," +
                     "{\"label\":\"400\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":2,\"isEmptyValue\":false},{\"id\":\"202007291426\",\"value\":8,\"isEmptyValue\":false}]}}," +
-                    "{\"label\":\"500\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":false},{\"id\":\"202007291426\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":false}]}}]"
+                    "{\"label\":\"500\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":true},{\"id\":\"202007291426\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":true}]}}]"
             },
             {
                 asList("200", "400"),
                 asList("202007291425", "202007291426"),
                 of("202007291425", new DataTable("200,1|400,2")),
-                "[{\"label\":\"200\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":1,\"isEmptyValue\":false},{\"id\":\"202007291426\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":false}]}}," +
-                    "{\"label\":\"400\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":2,\"isEmptyValue\":false},{\"id\":\"202007291426\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":false}]}}]"
+                "[{\"label\":\"200\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":1,\"isEmptyValue\":false},{\"id\":\"202007291426\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":true}]}}," +
+                    "{\"label\":\"400\",\"values\":{\"values\":[{\"id\":\"202007291425\",\"value\":2,\"isEmptyValue\":false},{\"id\":\"202007291426\",\"value\":" + DEFAULT_VALUE + ",\"isEmptyValue\":true}]}}]"
             },
         });
     }
@@ -108,7 +108,10 @@ public void testComposeLabelValue(final List<String> queryConditionLabels,
                                       final String expectedResult) {
         MetricsCondition condition = new MetricsCondition();
         condition.setName(MODULE_NAME);
-        List<MetricsValues> result = IMetricsQueryDAO.Util.composeLabelValue(condition, queryConditionLabels, datePoints, valueColumnData);
+        List<MetricsValues> result = IMetricsQueryDAO.Util.sortValues(
+            IMetricsQueryDAO.Util.composeLabelValue(condition, queryConditionLabels, valueColumnData),
+            datePoints, DEFAULT_VALUE
+        );
         assertThat(new Gson().toJson(result)).isEqualTo(expectedResult);
     }
 
diff --git a/test/e2e-v2/cases/apisix/expected/metrics-has-value-label.yml b/test/e2e-v2/cases/apisix/expected/metrics-has-value-label.yml
index 8ba301b70cc2..fb38843650ce 100644
--- a/test/e2e-v2/cases/apisix/expected/metrics-has-value-label.yml
+++ b/test/e2e-v2/cases/apisix/expected/metrics-has-value-label.yml
@@ -18,6 +18,12 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
-{{- end }}
\ No newline at end of file
+{{- end }}
diff --git a/test/e2e-v2/cases/aws/dynamodb/expected/metrics-has-value.yml b/test/e2e-v2/cases/aws/dynamodb/expected/metrics-has-value.yml
index ee6b0a2f26c5..04b077dd982c 100644
--- a/test/e2e-v2/cases/aws/dynamodb/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/aws/dynamodb/expected/metrics-has-value.yml
@@ -13,7 +13,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-  {{- contains . }}
+{{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ gt .value 0 }}
-  {{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/aws/eks/expected/metrics-has-value.yml b/test/e2e-v2/cases/aws/eks/expected/metrics-has-value.yml
index f7092ba3fac5..04b077dd982c 100644
--- a/test/e2e-v2/cases/aws/eks/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/aws/eks/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ gt .value 0 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/aws/s3/expected/metrics-has-value.yml b/test/e2e-v2/cases/aws/s3/expected/metrics-has-value.yml
index f7092ba3fac5..04b077dd982c 100644
--- a/test/e2e-v2/cases/aws/s3/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/aws/s3/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ gt .value 0 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/browser/expected/metrics-has-value-percentile.yml b/test/e2e-v2/cases/browser/expected/metrics-has-value-percentile.yml
index ef1749025048..c360120bcc2a 100644
--- a/test/e2e-v2/cases/browser/expected/metrics-has-value-percentile.yml
+++ b/test/e2e-v2/cases/browser/expected/metrics-has-value-percentile.yml
@@ -18,30 +18,60 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 1
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 2
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 3
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 4
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 {{- end }}
diff --git a/test/e2e-v2/cases/browser/expected/metrics-has-value.yml b/test/e2e-v2/cases/browser/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/browser/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/browser/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/cluster/expected/metrics-day-first.yml b/test/e2e-v2/cases/cluster/expected/metrics-day-first.yml
index 53bb3c9ae77f..cff0a0b2b64c 100644
--- a/test/e2e-v2/cases/cluster/expected/metrics-day-first.yml
+++ b/test/e2e-v2/cases/cluster/expected/metrics-day-first.yml
@@ -13,6 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 - key: 2022-01-26
-  value: 200
+  value:
+    value: 200
+    isemptyvalue: false
 - key: 2022-01-27
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
diff --git a/test/e2e-v2/cases/cluster/expected/metrics-day-second.yml b/test/e2e-v2/cases/cluster/expected/metrics-day-second.yml
index abd06f597833..a4acb73330b3 100644
--- a/test/e2e-v2/cases/cluster/expected/metrics-day-second.yml
+++ b/test/e2e-v2/cases/cluster/expected/metrics-day-second.yml
@@ -13,6 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 - key: 2022-01-26
-  value: 400
+  value:
+    value: 400
+    isemptyvalue: false
 - key: 2022-01-27
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
diff --git a/test/e2e-v2/cases/cluster/expected/metrics-hour-first.yml b/test/e2e-v2/cases/cluster/expected/metrics-hour-first.yml
index 096ffa7350ab..f3fb0ba08ae7 100644
--- a/test/e2e-v2/cases/cluster/expected/metrics-hour-first.yml
+++ b/test/e2e-v2/cases/cluster/expected/metrics-hour-first.yml
@@ -14,12 +14,22 @@
 # limitations under the License.
 
 - key: 2022-01-26 01
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 02
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 03
-  value: 200
+  value:
+    value: 200
+    isemptyvalue: false
 - key: 2022-01-26 04
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 05
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
diff --git a/test/e2e-v2/cases/cluster/expected/metrics-hour-second.yml b/test/e2e-v2/cases/cluster/expected/metrics-hour-second.yml
index 414d886ba8bd..105885e20ec2 100644
--- a/test/e2e-v2/cases/cluster/expected/metrics-hour-second.yml
+++ b/test/e2e-v2/cases/cluster/expected/metrics-hour-second.yml
@@ -14,12 +14,22 @@
 # limitations under the License.
 
 - key: 2022-01-26 01
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 02
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 03
-  value: 400
+  value:
+    value: 400
+    isemptyvalue: false
 - key: 2022-01-26 04
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 05
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
diff --git a/test/e2e-v2/cases/cluster/expected/metrics-minute-first.yml b/test/e2e-v2/cases/cluster/expected/metrics-minute-first.yml
index 2ab5e65462f3..9cc56bc1fa5d 100644
--- a/test/e2e-v2/cases/cluster/expected/metrics-minute-first.yml
+++ b/test/e2e-v2/cases/cluster/expected/metrics-minute-first.yml
@@ -14,8 +14,14 @@
 # limitations under the License.
 
 - key: 2022-01-26 0259
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 0300
-  value: 200
+  value:
+    value: 200
+    isemptyvalue: false
 - key: 2022-01-26 0301
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
diff --git a/test/e2e-v2/cases/cluster/expected/metrics-minute-second.yml b/test/e2e-v2/cases/cluster/expected/metrics-minute-second.yml
index 6a3c4aa15d2c..0d342500d38e 100644
--- a/test/e2e-v2/cases/cluster/expected/metrics-minute-second.yml
+++ b/test/e2e-v2/cases/cluster/expected/metrics-minute-second.yml
@@ -14,8 +14,14 @@
 # limitations under the License.
 
 - key: 2022-01-26 0309
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
 - key: 2022-01-26 0310
-  value: 200
+  value:
+    value: 200
+    isemptyvalue: false
 - key: 2022-01-26 0311
-  value: 0
+  value:
+    value: 0
+    isemptyvalue: true
diff --git a/test/e2e-v2/cases/gateway/expected/metrics-has-value-percentile.yml b/test/e2e-v2/cases/gateway/expected/metrics-has-value-percentile.yml
index ef1749025048..c360120bcc2a 100644
--- a/test/e2e-v2/cases/gateway/expected/metrics-has-value-percentile.yml
+++ b/test/e2e-v2/cases/gateway/expected/metrics-has-value-percentile.yml
@@ -18,30 +18,60 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 1
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 2
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 3
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 4
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 {{- end }}
diff --git a/test/e2e-v2/cases/gateway/expected/metrics-has-value.yml b/test/e2e-v2/cases/gateway/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/gateway/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/gateway/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/go/expected/metrics-has-value.yml b/test/e2e-v2/cases/go/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/go/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/go/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/istio/als/expected/metrics-has-value.yml b/test/e2e-v2/cases/istio/als/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/istio/als/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/istio/als/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/istio/metrics/expected/metrics-has-value.yml b/test/e2e-v2/cases/istio/metrics/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/istio/metrics/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/istio/metrics/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/kafka/meter/expected/metrics-has-value.yml b/test/e2e-v2/cases/kafka/meter/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/kafka/meter/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/kafka/meter/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/log/expected/metrics-has-value.yml b/test/e2e-v2/cases/log/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/log/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/log/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/lua/expected/metrics-has-value.yml b/test/e2e-v2/cases/lua/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/lua/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/lua/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/mariadb/expected/metrics-has-value.yml b/test/e2e-v2/cases/mariadb/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/mariadb/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/mariadb/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/meter/expected/metrics-has-value.yml b/test/e2e-v2/cases/meter/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/meter/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/meter/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/mysql/expected/metrics-has-value.yml b/test/e2e-v2/cases/mysql/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/mysql/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/mysql/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/nodejs/expected/metrics-has-value.yml b/test/e2e-v2/cases/nodejs/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/nodejs/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/nodejs/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/php/expected/metrics-has-value.yml b/test/e2e-v2/cases/php/expected/metrics-has-value.yml
index 87e3c2da20bc..04b077dd982c 100644
--- a/test/e2e-v2/cases/php/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/php/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 2 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/postgresql/expected/metrics-has-value-label.yml b/test/e2e-v2/cases/postgresql/expected/metrics-has-value-label.yml
index 8ba301b70cc2..fb38843650ce 100644
--- a/test/e2e-v2/cases/postgresql/expected/metrics-has-value-label.yml
+++ b/test/e2e-v2/cases/postgresql/expected/metrics-has-value-label.yml
@@ -18,6 +18,12 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
-{{- end }}
\ No newline at end of file
+{{- end }}
diff --git a/test/e2e-v2/cases/postgresql/expected/metrics-has-value.yml b/test/e2e-v2/cases/postgresql/expected/metrics-has-value.yml
index 2feacf9aa3bb..ea1c6bbf3a0d 100644
--- a/test/e2e-v2/cases/postgresql/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/postgresql/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }} 
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }} 
diff --git a/test/e2e-v2/cases/profiling/ebpf/continuous/expected/metrics-has-value.yml b/test/e2e-v2/cases/profiling/ebpf/continuous/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/profiling/ebpf/continuous/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/profiling/ebpf/continuous/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value-label.yml b/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value-label.yml
index a4becdf89cd4..fb38843650ce 100644
--- a/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value-label.yml
+++ b/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value-label.yml
@@ -18,6 +18,12 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 {{- end }}
diff --git a/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value.yml b/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/profiling/ebpf/network/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/python/expected/metrics-has-value.yml b/test/e2e-v2/cases/python/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/python/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/python/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/satellite/native-protocols/expected/metrics-has-value.yml b/test/e2e-v2/cases/satellite/native-protocols/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/satellite/native-protocols/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/satellite/native-protocols/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/simple/expected/metrics-has-value-percentile.yml b/test/e2e-v2/cases/simple/expected/metrics-has-value-percentile.yml
index ef1749025048..c360120bcc2a 100644
--- a/test/e2e-v2/cases/simple/expected/metrics-has-value-percentile.yml
+++ b/test/e2e-v2/cases/simple/expected/metrics-has-value-percentile.yml
@@ -18,30 +18,60 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 1
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 2
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 3
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 4
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 {{- end }}
diff --git a/test/e2e-v2/cases/simple/expected/metrics-has-value.yml b/test/e2e-v2/cases/simple/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/simple/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/simple/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/so11y/expected/metrics-has-value-label.yml b/test/e2e-v2/cases/so11y/expected/metrics-has-value-label.yml
index a4becdf89cd4..fb38843650ce 100644
--- a/test/e2e-v2/cases/so11y/expected/metrics-has-value-label.yml
+++ b/test/e2e-v2/cases/so11y/expected/metrics-has-value-label.yml
@@ -18,6 +18,12 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 {{- end }}
diff --git a/test/e2e-v2/cases/so11y/expected/metrics-has-value.yml b/test/e2e-v2/cases/so11y/expected/metrics-has-value.yml
index b922f84b394b..04b077dd982c 100644
--- a/test/e2e-v2/cases/so11y/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/so11y/expected/metrics-has-value.yml
@@ -13,7 +13,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-  {{- contains . }}
+{{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-  {{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/storage/expected/metrics-has-value-percentile.yml b/test/e2e-v2/cases/storage/expected/metrics-has-value-percentile.yml
index ef1749025048..c360120bcc2a 100644
--- a/test/e2e-v2/cases/storage/expected/metrics-has-value-percentile.yml
+++ b/test/e2e-v2/cases/storage/expected/metrics-has-value-percentile.yml
@@ -18,30 +18,60 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 1
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 2
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 3
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 - key: 4
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 {{- end }}
diff --git a/test/e2e-v2/cases/storage/expected/metrics-has-value.yml b/test/e2e-v2/cases/storage/expected/metrics-has-value.yml
index d9c49854c963..04b077dd982c 100644
--- a/test/e2e-v2/cases/storage/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/storage/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-{{- end }}
\ No newline at end of file
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/cases/storage/expected/metrics-nullable-single-sla-empty.yml b/test/e2e-v2/cases/storage/expected/metrics-nullable-single-sla-empty.yml
new file mode 100644
index 000000000000..25b001134f15
--- /dev/null
+++ b/test/e2e-v2/cases/storage/expected/metrics-nullable-single-sla-empty.yml
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+value: 0
+isemptyvalue: true
diff --git a/test/e2e-v2/cases/storage/expected/metrics-nullable-single-sla.yml b/test/e2e-v2/cases/storage/expected/metrics-nullable-single-sla.yml
new file mode 100644
index 000000000000..ee72d979d5f4
--- /dev/null
+++ b/test/e2e-v2/cases/storage/expected/metrics-nullable-single-sla.yml
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+value: 10000
+isemptyvalue: false
diff --git a/test/e2e-v2/cases/storage/expected/metrics-single-sla.yml b/test/e2e-v2/cases/storage/expected/metrics-single-sla.yml
new file mode 100644
index 000000000000..dd1d3a4013ac
--- /dev/null
+++ b/test/e2e-v2/cases/storage/expected/metrics-single-sla.yml
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+10000
diff --git a/test/e2e-v2/cases/storage/storage-cases.yaml b/test/e2e-v2/cases/storage/storage-cases.yaml
index 520fbe42f2b0..65b53eb71b52 100644
--- a/test/e2e-v2/cases/storage/storage-cases.yaml
+++ b/test/e2e-v2/cases/storage/storage-cases.yaml
@@ -57,6 +57,12 @@ cases:
       )
     expected: expected/trace-users-detail.yml
   # service metrics
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics single --name=service_sla --service-name=e2e-service-provider
+    expected: expected/metrics-single-sla.yml
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics nullable --name=service_sla --service-name=e2e-service-provider
+    expected: expected/metrics-nullable-single-sla.yml
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics nullable --name=service_sla --service-name=e2e-service-provider --start "2023-01-08" --end "2023-01-09"
+    expected: expected/metrics-nullable-single-sla-empty.yml
   - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics sorted --name service_apdex 5
     expected: expected/metrics-top-service.yml
   - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics top --name service_sla 5
diff --git a/test/e2e-v2/cases/ttl/expected/metrics-has-no-value.yml b/test/e2e-v2/cases/ttl/expected/metrics-has-no-value.yml
index 2e80f7a838cc..25b001134f15 100644
--- a/test/e2e-v2/cases/ttl/expected/metrics-has-no-value.yml
+++ b/test/e2e-v2/cases/ttl/expected/metrics-has-no-value.yml
@@ -13,7 +13,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-  {{- range . }}
-- key: {{ notEmpty .key }}
-  value: 0
-  {{- end }}
+value: 0
+isemptyvalue: true
diff --git a/test/e2e-v2/cases/ttl/expected/metrics-has-value.yml b/test/e2e-v2/cases/ttl/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/ttl/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/ttl/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/ttl/ttl-cases.yaml b/test/e2e-v2/cases/ttl/ttl-cases.yaml
index 2de1b183e6e8..5f15ad58d9ab 100644
--- a/test/e2e-v2/cases/ttl/ttl-cases.yaml
+++ b/test/e2e-v2/cases/ttl/ttl-cases.yaml
@@ -25,5 +25,5 @@
     # verify metrics has been deleted
     - query: |
         sleep 30;
-        swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=service_resp_time --service-id=ZTJlLXRlc3QtZGVzdC1zZXJ2aWNl.1 --start="-193h" --end="-191h" |yq e 'to_entries' -
+        swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics nullable --name=service_resp_time --service-id=ZTJlLXRlc3QtZGVzdC1zZXJ2aWNl.1 --start="-193h" --end="-191h"
       expected: expected/metrics-has-no-value.yml
diff --git a/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value.yml b/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value.yml
index f7092ba3fac5..04b077dd982c 100644
--- a/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ gt .value 0 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/vm/expected/metrics-has-value-label.yml b/test/e2e-v2/cases/vm/expected/metrics-has-value-label.yml
index a4becdf89cd4..fb38843650ce 100644
--- a/test/e2e-v2/cases/vm/expected/metrics-has-value-label.yml
+++ b/test/e2e-v2/cases/vm/expected/metrics-has-value-label.yml
@@ -18,6 +18,12 @@
   value:
   {{- contains .value }}
   - key: {{ notEmpty .key }}
-    value: {{ ge .value 1 }}
+    value:
+      value: 0
+      isemptyvalue: true
+  - key: {{ notEmpty .key }}
+    value:
+      value: {{ ge .value.value 1 }}
+      isemptyvalue: false
   {{- end }}
 {{- end }}
diff --git a/test/e2e-v2/cases/vm/expected/metrics-has-value.yml b/test/e2e-v2/cases/vm/expected/metrics-has-value.yml
index 5359e6d315d7..04b077dd982c 100644
--- a/test/e2e-v2/cases/vm/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/vm/expected/metrics-has-value.yml
@@ -15,5 +15,11 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
 {{- end }}
diff --git a/test/e2e-v2/cases/win/expected/metrics-has-value.yml b/test/e2e-v2/cases/win/expected/metrics-has-value.yml
index 75b746f204b2..04b077dd982c 100644
--- a/test/e2e-v2/cases/win/expected/metrics-has-value.yml
+++ b/test/e2e-v2/cases/win/expected/metrics-has-value.yml
@@ -13,7 +13,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-  {{- contains . }}
+{{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 1 }}
-  {{- end }}
+  value:
+    value: 0
+    isemptyvalue: true
+- key: {{ notEmpty .key }}
+  value:
+    value: {{ ge .value.value 1 }}
+    isemptyvalue: false
+{{- end }}
diff --git a/test/e2e-v2/script/env b/test/e2e-v2/script/env
index 074f84a38071..338a8753049c 100644
--- a/test/e2e-v2/script/env
+++ b/test/e2e-v2/script/env
@@ -25,4 +25,4 @@ SW_KUBERNETES_COMMIT_SHA=b670c41d94a82ddefcf466d54bab5c492d88d772
 SW_ROVER_COMMIT=fc8d074c6d34ecfee585a7097cbd5aef1ca680a5
 SW_BANYANDB_COMMIT=adbd3e87df7f84e5d1904fcf40476d2e81842058
 
-SW_CTL_COMMIT=f3eed66ee2ff330e3218fdc995b6f9952901e37c
\ No newline at end of file
+SW_CTL_COMMIT=23debb3b77426edd70192095a5fe9b0fc9031068