This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new d8e8897a725 branch-4.2: [fix](statistics) full analyze not collect hot 
value by default #63625 (#68298)
d8e8897a725 is described below

commit d8e8897a725771afb651145f2e66d81df7978058
Author: yujun <[email protected]>
AuthorDate: Mon Sep 21 18:15:45 2026 +0800

    branch-4.2: [fix](statistics) full analyze not collect hot value by default 
#63625 (#68298)
    
    cherry-pick: #63625
    
    Conflicts are limited to FE test files, because branch-4.2 statistics
    tests still use JMockit while master has moved to Mockito, and
    `TableNameInfo`/`PartitionNamesInfo` live in `org.apache.doris.info` on
    branch-4.2. The test additions were re-expressed in the branch-4.2
    idiom; main-source changes are identical to #63625.
    
    `testDoSampleAlwaysCollectsHotValue` also stubs
    `getSampleCollectInfo(..)` with a `SampleCollectInfo` instead of
    `useLinearAnalyzeTemplate()`, since branch-4.2 `doSample` picks the
    template through the sample-algorithm decision, and
    `AnalysisManagerTest` now uses the 4-arg `mockAnalyzeCommand(..)` helper
    so the Paimon row-count case keeps a non-null `AnalyzeProperties`.
    
    FE UT: AnalyzeTableCommandTest, OlapAnalysisTaskTest,
    AnalysisManagerTest, HMSAnalysisTaskTest, StatisticsUtilTest
---
 .../antlr4/org/apache/doris/nereids/DorisLexer.g4  |   1 +
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |   2 +
 .../apache/doris/analysis/AnalyzeProperties.java   |  20 ++++
 .../doris/nereids/parser/LogicalPlanBuilder.java   |   2 +
 .../trees/plans/commands/AnalyzeCommand.java       |   6 ++
 .../org/apache/doris/statistics/AnalysisInfo.java  |  10 +-
 .../doris/statistics/AnalysisInfoBuilder.java      |  11 +-
 .../apache/doris/statistics/AnalysisManager.java   |   3 +
 .../apache/doris/statistics/BaseAnalysisTask.java  |  23 ++++
 .../doris/statistics/ExternalAnalysisTask.java     |  16 +--
 .../apache/doris/statistics/OlapAnalysisTask.java  |  12 ++-
 .../doris/statistics/StatisticsAutoCollector.java  |   1 +
 .../plans/commands/AnalyzeTableCommandTest.java    |  66 +++++++++++-
 .../doris/statistics/AnalysisManagerTest.java      |  96 ++++++++++++++---
 .../doris/statistics/HMSAnalysisTaskTest.java      |  60 +++++++++++
 .../doris/statistics/OlapAnalysisTaskTest.java     | 117 +++++++++++++++++++++
 .../doris/statistics/util/StatisticsUtilTest.java  |   4 +
 .../distinct_split/disitinct_split.out             |  57 ++++------
 .../statistics/test_full_analyze_hot_value.groovy  |  59 +++++++----
 .../suites/statistics/test_hot_value.groovy        |   2 +-
 20 files changed, 481 insertions(+), 87 deletions(-)

diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
index 4a762c33201..4334e001e38 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
@@ -287,6 +287,7 @@ HISTOGRAM: 'HISTOGRAM';
 HLL: 'HLL';
 HLL_UNION: 'HLL_UNION';
 HOSTNAME: 'HOSTNAME';
+HOT: 'HOT';
 HOTSPOT: 'HOTSPOT';
 HOUR: 'HOUR';
 HOURS:  'HOURS';
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index 9ceaeef405f..372429744dc 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -917,6 +917,7 @@ analyzeProperties
     | FULL
     | SQL
     | HISTOGRAM
+    | (HOT VALUE)
     | (SAMPLE ((ROWS rows=INTEGER_VALUE) | (PERCENT percent=INTEGER_VALUE)) )
     | (BUCKETS bucket=INTEGER_VALUE)
     | (PERIOD periodInSecond=INTEGER_VALUE)
@@ -2175,6 +2176,7 @@ nonReserved
     | HISTOGRAM
     | HLL_UNION
     | HOSTNAME
+    | HOT
     | HOTSPOT
     | HOUR
     | HOURS
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/AnalyzeProperties.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/AnalyzeProperties.java
index 8c7deafb430..8c5f3671b77 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/AnalyzeProperties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/AnalyzeProperties.java
@@ -45,6 +45,7 @@ public class AnalyzeProperties {
     public static final String PROPERTY_FORCE_FULL = "force.full";
     public static final String PROPERTY_EXTERNAL_TABLE_USE_SQL = 
"external.table.use.sql";
     public static final String PROPERTY_USE_AUTO_ANALYZER = 
"use.auto.analyzer";
+    public static final String PROPERTY_COLLECT_HOT_VALUE = 
"collect.hot.value";
 
     public static final AnalyzeProperties DEFAULT_PROP = new 
AnalyzeProperties(new HashMap<String, String>() {
         {
@@ -74,6 +75,7 @@ public class AnalyzeProperties {
             .add(PROPERTY_FORCE_FULL)
             .add(PROPERTY_EXTERNAL_TABLE_USE_SQL)
             .add(PROPERTY_USE_AUTO_ANALYZER)
+            .add(PROPERTY_COLLECT_HOT_VALUE)
             .build();
 
     public AnalyzeProperties(Map<String, String> properties) {
@@ -96,6 +98,7 @@ public class AnalyzeProperties {
         checkAnalysisMode(msgTemplate);
         checkAnalysisType(msgTemplate);
         checkScheduleType(msgTemplate);
+        checkCollectHotValue(msgTemplate);
         checkPeriod();
     }
 
@@ -240,6 +243,15 @@ public class AnalyzeProperties {
         }
     }
 
+    private void checkCollectHotValue(String msgTemplate) throws 
AnalysisException {
+        if (properties.containsKey(PROPERTY_COLLECT_HOT_VALUE)) {
+            String value = properties.get(PROPERTY_COLLECT_HOT_VALUE);
+            if (!"true".equalsIgnoreCase(value) && 
!"false".equalsIgnoreCase(value)) {
+                throw new AnalysisException(String.format(msgTemplate, 
PROPERTY_COLLECT_HOT_VALUE, value));
+            }
+        }
+    }
+
     private void checkPeriod() throws AnalysisException {
         if (properties.containsKey(PROPERTY_PERIOD_SECONDS)
                 && properties.containsKey(PROPERTY_PERIOD_CRON)) {
@@ -287,6 +299,14 @@ public class AnalyzeProperties {
         return properties.containsKey(PROPERTY_EXTERNAL_TABLE_USE_SQL);
     }
 
+    public boolean hasCollectHotValue() {
+        return properties.containsKey(PROPERTY_COLLECT_HOT_VALUE);
+    }
+
+    public boolean collectHotValue() {
+        return 
Boolean.parseBoolean(properties.get(PROPERTY_COLLECT_HOT_VALUE));
+    }
+
     public String toSQL() {
         StringBuilder sb = new StringBuilder();
         sb.append("PROPERTIES(");
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 8804be4406c..43f762fe982 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -8584,6 +8584,8 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
             properties.put(AnalyzeProperties.PROPERTY_EXTERNAL_TABLE_USE_SQL, 
"true");
         } else if (ctx.HISTOGRAM() != null) {
             properties.put(AnalyzeProperties.PROPERTY_ANALYSIS_TYPE, 
AnalysisInfo.AnalysisType.HISTOGRAM.toString());
+        } else if (ctx.HOT() != null) {
+            properties.put(AnalyzeProperties.PROPERTY_COLLECT_HOT_VALUE, 
"true");
         } else if (ctx.SAMPLE() != null) {
             if (ctx.ROWS() != null) {
                 properties.put(AnalyzeProperties.PROPERTY_SAMPLE_ROWS, 
ctx.INTEGER_VALUE().getText());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
index 7c3d8ebe2c1..34323496fb4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
@@ -138,10 +138,16 @@ public abstract class AnalyzeCommand extends Command 
implements ForwardWithSync
         return analyzeProperties.usingSqlForExternalTable();
     }
 
+    /**
+     * Validate analyze command properties.
+     */
     public void validate(ConnectContext ctx) throws UserException {
         if (analyzeProperties != null) {
             analyzeProperties.check();
         }
+        if (analyzeProperties.hasCollectHotValue() && getAnalysisMethod() == 
AnalysisInfo.AnalysisMethod.SAMPLE) {
+            throw new AnalysisException("Sample analyze always collects hot 
value");
+        }
     }
 
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfo.java 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfo.java
index 58b2c3e3d1f..d2c293b33b1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfo.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfo.java
@@ -168,6 +168,9 @@ public class AnalysisInfo implements Writable {
     @SerializedName("usingSqlForExternalTable")
     public final boolean usingSqlForExternalTable;
 
+    @SerializedName("chv")
+    public final Boolean collectHotValue;
+
     @SerializedName("createTime")
     public final long createTime = System.currentTimeMillis();
 
@@ -209,8 +212,9 @@ public class AnalysisInfo implements Writable {
             long lastExecTimeInMs, long timeCostInMs, AnalysisState state, 
ScheduleType scheduleType,
             boolean partitionOnly, boolean samplingPartition,
             boolean isAllPartition, long partitionCount, CronExpression 
cronExpression, boolean forceFull,
-            boolean usingSqlForExternalTable, long tblUpdateTime, long 
rowCount, boolean userInject, long updateRows,
-            long tableVersion, JobPriority priority, Map<Long, Long> 
partitionUpdateRows, boolean enablePartition) {
+            boolean usingSqlForExternalTable, Boolean collectHotValue, long 
tblUpdateTime, long rowCount,
+            boolean userInject, long updateRows, long tableVersion, 
JobPriority priority,
+            Map<Long, Long> partitionUpdateRows, boolean enablePartition) {
         this.jobId = jobId;
         this.taskId = taskId;
         this.taskIds = taskIds;
@@ -243,6 +247,7 @@ public class AnalysisInfo implements Writable {
         }
         this.forceFull = forceFull;
         this.usingSqlForExternalTable = usingSqlForExternalTable;
+        this.collectHotValue = collectHotValue;
         this.tblUpdateTime = tblUpdateTime;
         this.rowCount = rowCount;
         this.userInject = userInject;
@@ -293,6 +298,7 @@ public class AnalysisInfo implements Writable {
         }
         sj.add("forceFull: " + forceFull);
         sj.add("usingSqlForExternalTable: " + usingSqlForExternalTable);
+        sj.add("collectHotValue: " + collectHotValue);
         sj.add("rowCount: " + rowCount);
         sj.add("userInject: " + userInject);
         sj.add("updateRows: " + updateRows);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfoBuilder.java 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfoBuilder.java
index bbd0d616495..d9fe7c21220 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfoBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfoBuilder.java
@@ -59,6 +59,7 @@ public class AnalysisInfoBuilder {
     private CronExpression cronExpression;
     private boolean forceFull;
     private boolean usingSqlForExternalTable;
+    private Boolean collectHotValue;
     private long tblUpdateTime;
     private long rowCount;
     private boolean userInject = false;
@@ -101,6 +102,7 @@ public class AnalysisInfoBuilder {
         cronExpression = info.cronExpression;
         forceFull = info.forceFull;
         usingSqlForExternalTable = info.usingSqlForExternalTable;
+        collectHotValue = info.collectHotValue;
         tblUpdateTime = info.tblUpdateTime;
         rowCount = info.rowCount;
         userInject = info.userInject;
@@ -256,6 +258,11 @@ public class AnalysisInfoBuilder {
         return this;
     }
 
+    public AnalysisInfoBuilder setCollectHotValue(Boolean collectHotValue) {
+        this.collectHotValue = collectHotValue;
+        return this;
+    }
+
     public AnalysisInfoBuilder setTblUpdateTime(long tblUpdateTime) {
         this.tblUpdateTime = tblUpdateTime;
         return this;
@@ -301,8 +308,8 @@ public class AnalysisInfoBuilder {
                 colName, indexId, jobType, analysisMethod, analysisType, 
samplePercent,
                 sampleRows, maxBucketNum, periodTimeInMs, message, 
lastExecTimeInMs, timeCostInMs, state, scheduleType,
                 partitionOnly, samplingPartition, isAllPartition, 
partitionCount,
-                cronExpression, forceFull, usingSqlForExternalTable, 
tblUpdateTime, rowCount, userInject, updateRows,
-                tableVersion, priority, partitionUpdateRows, enablePartition);
+                cronExpression, forceFull, usingSqlForExternalTable, 
collectHotValue, tblUpdateTime, rowCount,
+                userInject, updateRows, tableVersion, priority, 
partitionUpdateRows, enablePartition);
     }
 
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisManager.java 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisManager.java
index f6ac836a42d..1de58345dc1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisManager.java
@@ -381,6 +381,9 @@ public class AnalysisManager implements Writable {
         infoBuilder.setCronExpression(cronExpression);
         infoBuilder.setForceFull(command.forceFull());
         
infoBuilder.setUsingSqlForExternalTable(command.usingSqlForExternalTable());
+        AnalyzeProperties analyzeProperties = command.getAnalyzeProperties();
+        infoBuilder.setCollectHotValue((analyzeProperties.hasCollectHotValue()
+                && analyzeProperties.collectHotValue()) || analysisMethod == 
AnalysisMethod.SAMPLE);
         if (analysisMethod == AnalysisMethod.SAMPLE) {
             infoBuilder.setSamplePercent(samplePercent);
             infoBuilder.setSampleRows(sampleRows);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/BaseAnalysisTask.java 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/BaseAnalysisTask.java
index b9d8cd78dc6..2e0c5cc4a98 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/statistics/BaseAnalysisTask.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/BaseAnalysisTask.java
@@ -120,6 +120,25 @@ public abstract class BaseAnalysisTask {
             +         "GROUP BY `hash_value` ORDER BY `count` DESC LIMIT 
${hotValueCollectCount}) t) "
             + "SELECT * FROM cte2 CROSS JOIN cte3";
 
+    protected static final String FULL_ANALYZE_WITHOUT_HOT_VALUE_TEMPLATE =
+            "SELECT CONCAT(${tblId}, '-', ${idxId}, '-', '${colId}') AS `id`, "
+            +     "${catalogId} AS `catalog_id`, "
+            +     "${dbId} AS `db_id`, "
+            +     "${tblId} AS `tbl_id`, "
+            +     "${idxId} AS `idx_id`, "
+            +     "'${colId}' AS `col_id`, "
+            +     "NULL AS `part_id`, "
+            +     "COUNT(1) AS `row_count`, "
+            +     "NDV(`${colName}`) AS `ndv`, "
+            +     "COUNT(1) - COUNT(`${colName}`) AS `null_count`, "
+            +     "SUBSTRING(CAST(MIN(`${colName}`) AS STRING), 1, 1024) AS 
`min`, "
+            +     "SUBSTRING(CAST(MAX(`${colName}`) AS STRING), 1, 1024) AS 
`max`, "
+            +     "${dataSizeFunction} AS `data_size`, "
+            +     "NOW() AS `update_time`, "
+            +     "null as `hot_value` "
+            + "FROM (SELECT `${colName}`${lengthAssert} "
+            +     "FROM `${catalogName}`.`${dbName}`.`${tblName}` ${index}) 
__lc_t";
+
     protected static final String LINEAR_ANALYZE_TEMPLATE = "WITH cte1 AS ("
             +     "SELECT `${colName}`${lengthAssert} "
             +     "FROM `${catalogName}`.`${dbName}`.`${tblName}` ${index} 
${sampleHints} ${limit} ${preAggHint}), "
@@ -591,6 +610,10 @@ public abstract class BaseAnalysisTask {
 
     protected abstract void deleteNotExistPartitionStats(AnalysisInfo jobInfo) 
throws DdlException;
 
+    protected boolean shouldCollectHotValue() {
+        return Boolean.TRUE.equals(info.collectHotValue);
+    }
+
     protected String getPartitionInfo(String partitionName) {
         return "";
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/ExternalAnalysisTask.java
 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/ExternalAnalysisTask.java
index 8c4eccb1165..d37b3740f9d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/ExternalAnalysisTask.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/ExternalAnalysisTask.java
@@ -58,18 +58,22 @@ public class ExternalAnalysisTask extends BaseAnalysisTask {
     }
 
     protected void doFull() throws Exception {
-        StringBuilder sb = new StringBuilder();
         Map<String, String> params = buildSqlParams();
         params.put("dataSizeFunction", getDataSizeFunction(col, false));
-        params.put("hotValueCollectCount", 
String.valueOf(SessionVariable.getHotValueCollectCount()));
-        params.put("subStringColName", getStringTypeColName(col));
-        params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE `${colName}` 
IS NOT NULL)");
         if (LOG.isDebugEnabled()) {
             LOG.debug("Will do full collection for column {}", col.getName());
         }
-        sb.append(FULL_ANALYZE_TEMPLATE);
+        String template;
+        if (shouldCollectHotValue()) {
+            params.put("hotValueCollectCount", 
String.valueOf(SessionVariable.getHotValueCollectCount()));
+            params.put("subStringColName", getStringTypeColName(col));
+            params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE 
`${colName}` IS NOT NULL)");
+            template = FULL_ANALYZE_TEMPLATE;
+        } else {
+            template = FULL_ANALYZE_WITHOUT_HOT_VALUE_TEMPLATE;
+        }
         StringSubstitutor stringSubstitutor = new StringSubstitutor(params);
-        String sql = stringSubstitutor.replace(sb.toString());
+        String sql = stringSubstitutor.replace(template);
         runQuery(sql);
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java
index 287a5d7b3b9..a7ac364c984 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/statistics/OlapAnalysisTask.java
@@ -378,11 +378,15 @@ public class OlapAnalysisTask extends BaseAnalysisTask {
             doPartitionTable();
         } else {
             Map<String, String> params = buildSqlParams();
-            params.put("hotValueCollectCount", 
String.valueOf(SessionVariable.getHotValueCollectCount()));
-            params.put("subStringColName", getStringTypeColName(col));
-            params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE 
`${colName}` IS NOT NULL)");
             StringSubstitutor stringSubstitutor = new 
StringSubstitutor(params);
-            runQuery(stringSubstitutor.replace(FULL_ANALYZE_TEMPLATE));
+            if (shouldCollectHotValue()) {
+                params.put("hotValueCollectCount", 
String.valueOf(SessionVariable.getHotValueCollectCount()));
+                params.put("subStringColName", getStringTypeColName(col));
+                params.put("rowCount2", "(SELECT COUNT(1) FROM cte1 WHERE 
`${colName}` IS NOT NULL)");
+                runQuery(stringSubstitutor.replace(FULL_ANALYZE_TEMPLATE));
+            } else {
+                
runQuery(stringSubstitutor.replace(FULL_ANALYZE_WITHOUT_HOT_VALUE_TEMPLATE));
+            }
         }
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsAutoCollector.java
 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsAutoCollector.java
index 2ce0f05229b..0dbe97f1ba1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsAutoCollector.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/statistics/StatisticsAutoCollector.java
@@ -256,6 +256,7 @@ public class StatisticsAutoCollector extends MasterDaemon {
                 .setPartitionNames(Collections.emptySet())
                 .setSampleRows(analysisMethod.equals(AnalysisMethod.SAMPLE)
                     ? StatisticsUtil.getHugeTableSampleRows() : -1)
+                
.setCollectHotValue(analysisMethod.equals(AnalysisMethod.SAMPLE))
                 .setScheduleType(ScheduleType.AUTOMATIC)
                 .setState(AnalysisState.PENDING)
                 .setTaskIds(new ArrayList<>())
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
index 5d8f14938a0..a5f16003984 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
@@ -26,7 +26,10 @@ import org.apache.doris.info.PartitionNamesInfo;
 import org.apache.doris.info.TableNameInfo;
 import org.apache.doris.mysql.privilege.AccessControllerManager;
 import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.statistics.AnalysisInfo;
 
 import com.google.common.collect.ImmutableList;
 import mockit.Expectations;
@@ -34,6 +37,9 @@ import mockit.Mocked;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+
 public class AnalyzeTableCommandTest {
     private static final String internalCtl = 
InternalCatalog.INTERNAL_CATALOG_NAME;
     @Mocked
@@ -82,5 +88,63 @@ public class AnalyzeTableCommandTest {
                 () -> 
analyzeTableCommand.checkAnalyzePrivilege(tableNameInfo2),
                 "ANALYZE command denied to user 'null'@'null' for table 
'test_db: test_tbl2'");
     }
-}
 
+    @Test
+    void testCollectHotValueDefaultAndOverride() {
+        TableNameInfo tableNameInfo = new TableNameInfo(internalCtl,
+                CatalogMocker.TEST_DB_NAME, CatalogMocker.TEST_TBL_NAME);
+
+        AnalyzeTableCommand fullAnalyzeCommand = new 
AnalyzeTableCommand(tableNameInfo,
+                null, null, new AnalyzeProperties(defaultAnalyzeProperties()));
+        
Assertions.assertFalse(fullAnalyzeCommand.getAnalyzeProperties().hasCollectHotValue());
+        Assertions.assertEquals(AnalysisInfo.AnalysisMethod.FULL, 
fullAnalyzeCommand.getAnalysisMethod());
+
+        Map<String, String> sampleProperties = defaultAnalyzeProperties();
+        sampleProperties.put(AnalyzeProperties.PROPERTY_SAMPLE_ROWS, "100");
+        AnalyzeTableCommand sampleAnalyzeCommand = new 
AnalyzeTableCommand(tableNameInfo,
+                null, null, new AnalyzeProperties(sampleProperties));
+        
Assertions.assertFalse(sampleAnalyzeCommand.getAnalyzeProperties().hasCollectHotValue());
+        Assertions.assertEquals(AnalysisInfo.AnalysisMethod.SAMPLE, 
sampleAnalyzeCommand.getAnalysisMethod());
+
+        Map<String, String> forcedFullHotValueProperties = 
defaultAnalyzeProperties();
+        
forcedFullHotValueProperties.put(AnalyzeProperties.PROPERTY_COLLECT_HOT_VALUE, 
"true");
+        AnalyzeTableCommand forcedFullHotValueCommand = new 
AnalyzeTableCommand(tableNameInfo,
+                null, null, new 
AnalyzeProperties(forcedFullHotValueProperties));
+        
Assertions.assertTrue(forcedFullHotValueCommand.getAnalyzeProperties().collectHotValue());
+    }
+
+    @Test
+    void testCollectHotValuePropertyValidation() {
+        Map<String, String> properties = defaultAnalyzeProperties();
+        properties.put(AnalyzeProperties.PROPERTY_COLLECT_HOT_VALUE, 
"invalid");
+        AnalyzeProperties analyzeProperties = new 
AnalyzeProperties(properties);
+        Assertions.assertThrows(AnalysisException.class, 
analyzeProperties::check);
+    }
+
+    @Test
+    void testParseWithHotValue() {
+        NereidsParser parser = new NereidsParser();
+        LogicalPlan plan = parser.parseSingle("ANALYZE TABLE test_db.test_tbl 
WITH HOT VALUE");
+        Assertions.assertTrue(plan instanceof AnalyzeTableCommand);
+        AnalyzeTableCommand command = (AnalyzeTableCommand) plan;
+        
Assertions.assertTrue(command.getAnalyzeProperties().hasCollectHotValue());
+        
Assertions.assertTrue(command.getAnalyzeProperties().collectHotValue());
+    }
+
+    @Test
+    void testSampleAnalyzeWithHotValueRejected() {
+        NereidsParser parser = new NereidsParser();
+        AnalyzeTableCommand command = (AnalyzeTableCommand) parser.parseSingle(
+                "ANALYZE TABLE test_db.test_tbl WITH SAMPLE ROWS 100 WITH HOT 
VALUE");
+        AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                () -> command.validate(ctx));
+        Assertions.assertEquals("Sample analyze always collects hot value", 
exception.getDetailMessage());
+    }
+
+    private Map<String, String> defaultAnalyzeProperties() {
+        Map<String, String> properties = new HashMap<>();
+        properties.put(AnalyzeProperties.PROPERTY_SYNC, "false");
+        properties.put(AnalyzeProperties.PROPERTY_ANALYSIS_TYPE, 
"FUNDAMENTALS");
+        return properties;
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/AnalysisManagerTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/AnalysisManagerTest.java
index 4a290a2436b..f2c1f313044 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/AnalysisManagerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/AnalysisManagerTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.statistics;
 
+import org.apache.doris.analysis.AnalyzeProperties;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.Database;
 import org.apache.doris.catalog.DatabaseIf;
@@ -27,6 +28,7 @@ import org.apache.doris.catalog.Table;
 import org.apache.doris.catalog.TableIf;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.Pair;
 import org.apache.doris.datasource.CatalogIf;
 import org.apache.doris.datasource.InternalCatalog;
 import org.apache.doris.datasource.paimon.PaimonExternalCatalog;
@@ -34,6 +36,7 @@ import 
org.apache.doris.datasource.paimon.PaimonExternalDatabase;
 import org.apache.doris.datasource.paimon.PaimonExternalTable;
 import org.apache.doris.datasource.paimon.PaimonUtils;
 import org.apache.doris.info.PartitionNamesInfo;
+import org.apache.doris.info.TableNameInfo;
 import org.apache.doris.nereids.trees.expressions.ExprId;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
@@ -77,6 +80,67 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 // CHECKSTYLE OFF
 public class AnalysisManagerTest {
+    @Test
+    public void testBuildAnalysisJobInfoCollectHotValueDefault() {
+        AnalysisManager manager = new AnalysisManager();
+        Env env = Mockito.mock(Env.class);
+        try (MockedStatic<Env> envMockedStatic = 
Mockito.mockStatic(Env.class)) {
+            envMockedStatic.when(Env::getCurrentEnv).thenReturn(env);
+            Mockito.when(env.getNextId()).thenReturn(1L, 2L, 3L, 4L);
+
+            AnalysisInfo fullOnce = manager.buildAnalysisJobInfo(
+                    mockAnalyzeCommand(AnalysisMethod.FULL, ScheduleType.ONCE, 
false, false));
+            Assertions.assertFalse(fullOnce.collectHotValue);
+
+            AnalysisInfo samplePeriod = manager.buildAnalysisJobInfo(
+                    mockAnalyzeCommand(AnalysisMethod.SAMPLE, 
ScheduleType.PERIOD, false, true));
+            Assertions.assertTrue(samplePeriod.collectHotValue);
+
+            AnalysisInfo automatic = manager.buildAnalysisJobInfo(
+                    mockAnalyzeCommand(AnalysisMethod.FULL, 
ScheduleType.AUTOMATIC, false, true));
+            Assertions.assertFalse(automatic.collectHotValue);
+
+            AnalysisInfo automaticSample = manager.buildAnalysisJobInfo(
+                    mockAnalyzeCommand(AnalysisMethod.SAMPLE, 
ScheduleType.AUTOMATIC, false, true));
+            Assertions.assertTrue(automaticSample.collectHotValue);
+
+            AnalysisInfo explicitAutomatic = manager.buildAnalysisJobInfo(
+                    mockAnalyzeCommand(AnalysisMethod.FULL, 
ScheduleType.AUTOMATIC, true, false));
+            Assertions.assertFalse(explicitAutomatic.collectHotValue);
+        }
+    }
+
+    @Test
+    public void testBuildAnalysisJobInfoAutoSampleCommandCollectsHotValue() {
+        AnalysisManager manager = new AnalysisManager();
+        Env env = Mockito.mock(Env.class);
+        try (MockedStatic<Env> envMockedStatic = 
Mockito.mockStatic(Env.class)) {
+            envMockedStatic.when(Env::getCurrentEnv).thenReturn(env);
+            Mockito.when(env.getNextId()).thenReturn(1L);
+
+            Map<String, String> properties = new HashMap<>();
+            properties.put(AnalyzeProperties.PROPERTY_SYNC, "false");
+            properties.put(AnalyzeProperties.PROPERTY_ANALYSIS_TYPE, 
AnalysisType.FUNDAMENTALS.toString());
+            properties.put(AnalyzeProperties.PROPERTY_AUTOMATIC, "true");
+            properties.put(AnalyzeProperties.PROPERTY_SAMPLE_ROWS, "100");
+            AnalyzeTableCommand command = Mockito.spy(new AnalyzeTableCommand(
+                    new TableNameInfo(InternalCatalog.INTERNAL_CATALOG_NAME, 
"testDb", "testTbl"),
+                    null, ImmutableList.of("testCol"), new 
AnalyzeProperties(properties)));
+            TableIf table = Mockito.mock(TableIf.class);
+            Mockito.when(table.getId()).thenReturn(30001L);
+            Mockito.when(table.getColumnIndexPairs(Mockito.any()))
+                    .thenReturn(Collections.singleton(Pair.of("testTbl", 
"testCol")));
+            Mockito.doReturn(table).when(command).getTable();
+            Mockito.doReturn(10001L).when(command).getCatalogId();
+            Mockito.doReturn(20001L).when(command).getDbId();
+
+            AnalysisInfo analysisInfo = manager.buildAnalysisJobInfo(command);
+            Assertions.assertEquals(ScheduleType.AUTOMATIC, 
analysisInfo.scheduleType);
+            Assertions.assertEquals(AnalysisMethod.SAMPLE, 
analysisInfo.analysisMethod);
+            Assertions.assertTrue(analysisInfo.collectHotValue);
+        }
+    }
+
     @Test
     public void testUpdateTaskStatus(@Mocked BaseAnalysisTask task1,
             @Mocked BaseAnalysisTask task2) {
@@ -429,7 +493,7 @@ public class AnalysisManagerTest {
         PaimonExternalTable table = Mockito.spy(new 
TestPaimonExternalTable(catalog, database));
         Mockito.doReturn(-1L).when(table).getRowCount();
         
Mockito.doReturn(Collections.emptySet()).when(table).getColumnIndexPairs(Mockito.any());
-        AnalyzeTableCommand command = mockAnalyzeCommand(AnalysisMethod.FULL, 
ScheduleType.ONCE);
+        AnalyzeTableCommand command = mockAnalyzeCommand(AnalysisMethod.FULL, 
ScheduleType.ONCE, false, false);
         Mockito.when(command.getTable()).thenReturn(table);
         AnalysisManager manager = new AnalysisManager();
         Env env = Mockito.mock(Env.class);
@@ -475,11 +539,26 @@ public class AnalysisManagerTest {
         Assertions.assertTrue(count.get() <= 20);
     }
 
-    private AnalyzeTableCommand mockAnalyzeCommand(AnalysisMethod 
analysisMethod, ScheduleType scheduleType) {
+    private static class TestPaimonExternalTable extends PaimonExternalTable {
+        private TestPaimonExternalTable(PaimonExternalCatalog catalog, 
PaimonExternalDatabase database) {
+            super(30001L, "table", "table", catalog, database);
+        }
+
+        @Override
+        protected synchronized void makeSureInitialized() {
+        }
+    }
+
+    private AnalyzeTableCommand mockAnalyzeCommand(AnalysisMethod 
analysisMethod, ScheduleType scheduleType,
+            boolean hasCollectHotValue, boolean collectHotValue) {
         AnalyzeTableCommand command = Mockito.mock(AnalyzeTableCommand.class);
         TableIf table = Mockito.mock(TableIf.class);
+        Map<String, String> properties = new HashMap<>();
+        if (hasCollectHotValue) {
+            properties.put(AnalyzeProperties.PROPERTY_COLLECT_HOT_VALUE, 
String.valueOf(collectHotValue));
+        }
+        AnalyzeProperties analyzeProperties = new 
AnalyzeProperties(properties);
         Mockito.when(table.getId()).thenReturn(30001L);
-        
Mockito.when(table.getColumnIndexPairs(Mockito.any())).thenReturn(Collections.emptySet());
         Mockito.when(command.getTable()).thenReturn(table);
         
Mockito.when(command.getColumnNames()).thenReturn(Collections.emptySet());
         Mockito.when(command.isPartitionOnly()).thenReturn(false);
@@ -497,16 +576,7 @@ public class AnalysisManagerTest {
         
Mockito.when(command.getPartitionNames()).thenReturn(Collections.emptySet());
         Mockito.when(command.forceFull()).thenReturn(false);
         Mockito.when(command.usingSqlForExternalTable()).thenReturn(false);
+        
Mockito.when(command.getAnalyzeProperties()).thenReturn(analyzeProperties);
         return command;
     }
-
-    private static class TestPaimonExternalTable extends PaimonExternalTable {
-        private TestPaimonExternalTable(PaimonExternalCatalog catalog, 
PaimonExternalDatabase database) {
-            super(30001L, "table", "table", catalog, database);
-        }
-
-        @Override
-        protected synchronized void makeSureInitialized() {
-        }
-    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/HMSAnalysisTaskTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/HMSAnalysisTaskTest.java
index 078ca196df2..33721e5f8e4 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/HMSAnalysisTaskTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/HMSAnalysisTaskTest.java
@@ -199,6 +199,65 @@ public class HMSAnalysisTaskTest {
             }
         };
 
+        new MockUp<HMSAnalysisTask>() {
+            @Mock
+            public void runQuery(String sql) {
+                Assertions.assertEquals("SELECT CONCAT(30001, '-', -1, '-', 
'hour') AS `id`, "
+                        + "10001 AS `catalog_id`, 20001 AS `db_id`, 30001 AS 
`tbl_id`, "
+                        + "-1 AS `idx_id`, 'hour' AS `col_id`, NULL AS 
`part_id`, "
+                        + "COUNT(1) AS `row_count`, NDV(`hour`) AS `ndv`, "
+                        + "COUNT(1) - COUNT(`hour`) AS `null_count`, "
+                        + "SUBSTRING(CAST(MIN(`hour`) AS STRING), 1, 1024) AS 
`min`, "
+                        + "SUBSTRING(CAST(MAX(`hour`) AS STRING), 1, 1024) AS 
`max`, "
+                        + "COUNT(1) * 4 AS `data_size`, NOW() AS 
`update_time`, null as `hot_value` "
+                        + "FROM (SELECT `hour` FROM `hms`.`default`.`test` ) 
__lc_t", sql);
+            }
+        };
+
+        HMSAnalysisTask task = new HMSAnalysisTask();
+        task.col = new Column("hour", PrimitiveType.INT);
+        task.tbl = tableIf;
+        task.catalog = catalogIf;
+        task.db = databaseIf;
+        task.setTable(tableIf);
+
+        AnalysisInfoBuilder analysisInfoBuilder = new AnalysisInfoBuilder();
+        analysisInfoBuilder.setColName("hour");
+        analysisInfoBuilder.setJobType(AnalysisInfo.JobType.MANUAL);
+        analysisInfoBuilder.setUsingSqlForExternalTable(true);
+        task.info = analysisInfoBuilder.build();
+
+        task.doExecute();
+    }
+
+    @Test
+    public void testOrdinaryStatsWithHotValue(@Mocked CatalogIf catalogIf, 
@Mocked DatabaseIf databaseIf, @Mocked HMSExternalTable tableIf)
+            throws Exception {
+
+        new Expectations() {
+            {
+                tableIf.getId();
+                result = 30001;
+                tableIf.getName();
+                result = "test";
+                catalogIf.getId();
+                result = 10001;
+                catalogIf.getName();
+                result = "hms";
+                databaseIf.getId();
+                result = 20001;
+                databaseIf.getFullName();
+                result = "default";
+            }
+        };
+
+        new MockUp<HMSExternalTable>() {
+            @Mock
+            public Set<String> getPartitionNames() {
+                return ImmutableSet.of("date=20230101/hour=12");
+            }
+        };
+
         new MockUp<SessionVariable>() {
             @Mock
             public int getHotValueCollectCount() {
@@ -242,6 +301,7 @@ public class HMSAnalysisTaskTest {
         analysisInfoBuilder.setColName("hour");
         analysisInfoBuilder.setJobType(AnalysisInfo.JobType.MANUAL);
         analysisInfoBuilder.setUsingSqlForExternalTable(true);
+        analysisInfoBuilder.setCollectHotValue(true);
         task.info = analysisInfoBuilder.build();
 
         task.doExecute();
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
index af8c9c18f66..dd0113bf930 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/OlapAnalysisTaskTest.java
@@ -41,6 +41,7 @@ import org.apache.doris.common.FeConstants;
 import org.apache.doris.common.Pair;
 import org.apache.doris.common.util.DebugPointUtil;
 import org.apache.doris.datasource.CatalogIf;
+import org.apache.doris.persist.gson.GsonUtils;
 import org.apache.doris.qe.SessionVariable;
 import org.apache.doris.statistics.AnalysisInfo.AnalysisMethod;
 import org.apache.doris.statistics.AnalysisInfo.JobType;
@@ -58,6 +59,7 @@ import mockit.Mocked;
 import org.apache.commons.text.StringSubstitutor;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
@@ -91,6 +93,30 @@ public class OlapAnalysisTaskTest {
         Assertions.assertFalse(tableSample.isPercent());
     }
 
+    @Test
+    public void testShouldCollectHotValue() {
+        OlapAnalysisTask olapAnalysisTask = new OlapAnalysisTask();
+        olapAnalysisTask.info = new AnalysisInfoBuilder().build();
+        Assertions.assertFalse(olapAnalysisTask.shouldCollectHotValue());
+
+        olapAnalysisTask.info = new 
AnalysisInfoBuilder().setCollectHotValue(true).build();
+        Assertions.assertTrue(olapAnalysisTask.shouldCollectHotValue());
+
+        olapAnalysisTask.info = new 
AnalysisInfoBuilder().setCollectHotValue(false).build();
+        Assertions.assertFalse(olapAnalysisTask.shouldCollectHotValue());
+    }
+
+    @Test
+    public void testCollectHotValueUseShortSerializedName() {
+        AnalysisInfo info = new 
AnalysisInfoBuilder().setCollectHotValue(false).build();
+        String json = GsonUtils.GSON.toJson(info);
+        Assertions.assertTrue(json.contains("\"chv\":false"));
+        Assertions.assertFalse(json.contains("collectHotValue"));
+
+        AnalysisInfo deserialized = GsonUtils.GSON.fromJson(json, 
AnalysisInfo.class);
+        Assertions.assertFalse(deserialized.collectHotValue);
+    }
+
     // test auto small table
     @Test
     public void testSample3(@Mocked OlapTable tbl) {
@@ -1040,6 +1066,7 @@ public class OlapAnalysisTaskTest {
             AnalysisInfoBuilder builder = new AnalysisInfoBuilder();
             builder.setJobType(AnalysisInfo.JobType.MANUAL);
             builder.setColName("testCol");
+            builder.setCollectHotValue(true);
             task.info = builder.build();
             task.catalog = catalogIf;
             task.db = databaseIf;
@@ -1047,6 +1074,95 @@ public class OlapAnalysisTaskTest {
         }
     }
 
+    @Test
+    public void testDoFullWithoutHotValue() throws Exception {
+        CatalogIf catalogIf = Mockito.mock(CatalogIf.class);
+        DatabaseIf databaseIf = Mockito.mock(DatabaseIf.class);
+        OlapTable tableIf = Mockito.mock(OlapTable.class);
+        Mockito.when(tableIf.getId()).thenReturn(30001L);
+        Mockito.when(tableIf.getName()).thenReturn("testTbl");
+        Mockito.when(catalogIf.getId()).thenReturn(10001L);
+        Mockito.when(catalogIf.getName()).thenReturn("catalogName");
+        Mockito.when(databaseIf.getId()).thenReturn(20001L);
+        Mockito.when(databaseIf.getFullName()).thenReturn("testDb");
+
+        try (MockedStatic<StatisticsUtil> mockedStatisticsUtil = 
Mockito.mockStatic(
+                StatisticsUtil.class, Mockito.CALLS_REAL_METHODS)) {
+            
mockedStatisticsUtil.when(StatisticsUtil::enablePartitionAnalyze).thenReturn(false);
+
+            OlapAnalysisTask task = Mockito.spy(new OlapAnalysisTask());
+            Mockito.doAnswer(invocation -> {
+                String sql = invocation.getArgument(0);
+                Assertions.assertTrue(sql.startsWith("SELECT CONCAT(30001, 
'-', -1, '-', 'testCol') AS `id`"), sql);
+                Assertions.assertTrue(sql.contains("null as `hot_value`"), 
sql);
+                Assertions.assertTrue(sql.contains(
+                        "FROM (SELECT `testCol` FROM 
`catalogName`.`testDb`.`testTbl` ) __lc_t"), sql);
+                Assertions.assertFalse(sql.contains("CROSS JOIN cte3"), sql);
+                Assertions.assertFalse(sql.contains("GROUP BY `hash_value`"), 
sql);
+                return null;
+            }).when(task).runQuery(Mockito.anyString());
+
+            task.col = new Column("testCol", 
Type.fromPrimitiveType(PrimitiveType.INT),
+                true, null, null, null);
+            task.tbl = tableIf;
+            AnalysisInfoBuilder builder = new AnalysisInfoBuilder();
+            builder.setJobType(AnalysisInfo.JobType.MANUAL);
+            builder.setColName("testCol");
+            builder.setCollectHotValue(false);
+            task.info = builder.build();
+            task.catalog = catalogIf;
+            task.db = databaseIf;
+            task.doFull();
+        }
+    }
+
+    @Test
+    public void testDoSampleAlwaysCollectsHotValue() throws Exception {
+        CatalogIf catalogIf = Mockito.mock(CatalogIf.class);
+        DatabaseIf databaseIf = Mockito.mock(DatabaseIf.class);
+        OlapTable tableIf = Mockito.mock(OlapTable.class);
+        Mockito.when(tableIf.getId()).thenReturn(30001L);
+        Mockito.when(tableIf.getName()).thenReturn("testTbl");
+        Mockito.when(tableIf.getRowCount()).thenReturn(1000L);
+        Mockito.when(catalogIf.getId()).thenReturn(10001L);
+        Mockito.when(catalogIf.getName()).thenReturn("catalogName");
+        Mockito.when(databaseIf.getId()).thenReturn(20001L);
+        Mockito.when(databaseIf.getFullName()).thenReturn("testDb");
+
+        OlapAnalysisTask task = Mockito.spy(new OlapAnalysisTask());
+        Mockito.doReturn(new ResultRow(Lists.newArrayList("1", 
"2"))).when(task).collectMinMax();
+        Mockito.doNothing().when(task).getSampleParams(ArgumentMatchers.any(), 
ArgumentMatchers.anyLong(),
+                ArgumentMatchers.any());
+        Mockito.doAnswer(invocation -> {
+            String sql = invocation.getArgument(0);
+            Assertions.assertTrue(sql.contains("as `hot_value`"), sql);
+            Assertions.assertTrue(sql.contains("cte3"), sql);
+            Assertions.assertTrue(sql.contains("CROSS JOIN cte3"), sql);
+            Assertions.assertTrue(sql.contains("LIMIT 10"), sql);
+            Assertions.assertFalse(sql.contains("null as `hot_value`"), sql);
+            return null;
+        }).when(task).runQuery(Mockito.anyString());
+
+        task.col = new Column("testCol", 
Type.fromPrimitiveType(PrimitiveType.INT),
+                true, null, null, null);
+        task.tbl = tableIf;
+        AnalysisInfoBuilder builder = new AnalysisInfoBuilder();
+        builder.setJobType(AnalysisInfo.JobType.MANUAL);
+        builder.setColName("testCol");
+        builder.setCollectHotValue(false);
+        task.info = builder.build();
+        task.catalog = catalogIf;
+        task.db = databaseIf;
+        task.tableSample = new TableSample(false, 100L);
+
+        Mockito.doReturn(new 
OlapAnalysisTask.SampleCollectInfo(AnalyzeSampleAlgorithm.LINEAR, null))
+                .when(task).getSampleCollectInfo(ArgumentMatchers.anyLong());
+        task.doSample();
+        Mockito.doReturn(new 
OlapAnalysisTask.SampleCollectInfo(AnalyzeSampleAlgorithm.DUJ1, null))
+                .when(task).getSampleCollectInfo(ArgumentMatchers.anyLong());
+        task.doSample();
+    }
+
     @Test
     public void testDoFullHotValueStringColumn() throws Exception {
         CatalogIf catalogIf = Mockito.mock(CatalogIf.class);
@@ -1080,6 +1196,7 @@ public class OlapAnalysisTaskTest {
             AnalysisInfoBuilder builder = new AnalysisInfoBuilder();
             builder.setJobType(AnalysisInfo.JobType.MANUAL);
             builder.setColName("strCol");
+            builder.setCollectHotValue(true);
             task.info = builder.build();
             task.catalog = catalogIf;
             task.db = databaseIf;
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/util/StatisticsUtilTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/util/StatisticsUtilTest.java
index 02356bb094e..2cf25135784 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/statistics/util/StatisticsUtilTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/statistics/util/StatisticsUtilTest.java
@@ -643,6 +643,10 @@ class StatisticsUtilTest {
 
     @Test
     void testGetHotValues() {
+        Assertions.assertNull(StatisticsUtil.getHotValues(null, Type.INT));
+        Assertions.assertNull(StatisticsUtil.getHotValues("null", Type.INT));
+        Assertions.assertTrue(StatisticsUtil.getHotValues("", 
Type.INT).isEmpty());
+
         String value1 = "1234 :0.35 ;222 :0.34";
         Map<Literal, Float> hotValues = StatisticsUtil.getHotValues(value1, 
Type.INT);
         Map<Literal, Float> hotValuesAfterFilter = 
StatisticsUtil.getHotValuesWithOriginalThreshold(hotValues, 100);
diff --git 
a/regression-test/data/nereids_rules_p0/distinct_split/disitinct_split.out 
b/regression-test/data/nereids_rules_p0/distinct_split/disitinct_split.out
index 9a18cab3879..b239f4d54f8 100644
--- a/regression-test/data/nereids_rules_p0/distinct_split/disitinct_split.out
+++ b/regression-test/data/nereids_rules_p0/distinct_split/disitinct_split.out
@@ -369,13 +369,11 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ------hashAgg[DISTINCT_GLOBAL]
 --------hashAgg[DISTINCT_LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ------hashAgg[DISTINCT_GLOBAL]
 --------hashAgg[DISTINCT_LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !multi_count_mulitcols_with_gby --
 PhysicalCteAnchor ( cteId=CTEId#0 )
@@ -386,13 +384,11 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !three_count_mulitcols_without_gby --
 PhysicalCteAnchor ( cteId=CTEId#0 )
@@ -404,18 +400,15 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 --------hashAgg[DISTINCT_GLOBAL]
 ----------hashAgg[DISTINCT_LOCAL]
 ------------hashAgg[GLOBAL]
---------------hashAgg[LOCAL]
-----------------PhysicalCteConsumer ( cteId=CTEId#0 )
+--------------PhysicalCteConsumer ( cteId=CTEId#0 )
 --------hashAgg[DISTINCT_GLOBAL]
 ----------hashAgg[DISTINCT_LOCAL]
 ------------hashAgg[GLOBAL]
---------------hashAgg[LOCAL]
-----------------PhysicalCteConsumer ( cteId=CTEId#0 )
+--------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ------hashAgg[DISTINCT_GLOBAL]
 --------hashAgg[DISTINCT_LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !four_count_mulitcols_with_gby --
 PhysicalCteAnchor ( cteId=CTEId#0 )
@@ -428,23 +421,19 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ----------hashAgg[GLOBAL]
 ------------hashAgg[LOCAL]
 --------------hashAgg[GLOBAL]
-----------------hashAgg[LOCAL]
-------------------PhysicalCteConsumer ( cteId=CTEId#0 )
+----------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ----------hashAgg[GLOBAL]
 ------------hashAgg[LOCAL]
 --------------hashAgg[GLOBAL]
-----------------hashAgg[LOCAL]
-------------------PhysicalCteConsumer ( cteId=CTEId#0 )
+----------------PhysicalCteConsumer ( cteId=CTEId#0 )
 --------hashAgg[GLOBAL]
 ----------hashAgg[LOCAL]
 ------------hashAgg[GLOBAL]
---------------hashAgg[LOCAL]
-----------------PhysicalCteConsumer ( cteId=CTEId#0 )
+--------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !has_other_func --
 PhysicalCteAnchor ( cteId=CTEId#0 )
@@ -478,13 +467,11 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ----------hashAgg[GLOBAL]
 ------------hashAgg[LOCAL]
 --------------hashAgg[GLOBAL]
-----------------hashAgg[LOCAL]
-------------------PhysicalCteConsumer ( cteId=CTEId#0 )
+----------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ----------hashAgg[GLOBAL]
 ------------hashAgg[LOCAL]
 --------------hashAgg[GLOBAL]
-----------------hashAgg[LOCAL]
-------------------PhysicalCteConsumer ( cteId=CTEId#0 )
+----------------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !multi_count_with_gby --
 PhysicalCteAnchor ( cteId=CTEId#0 )
@@ -495,13 +482,11 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !multi_sum_with_gby --
 PhysicalCteAnchor ( cteId=CTEId#0 )
@@ -512,13 +497,11 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !sum_count_with_gby --
 PhysicalCteAnchor ( cteId=CTEId#0 )
@@ -529,12 +512,10 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[LOCAL]
 ----------hashAgg[GLOBAL]
-------------hashAgg[LOCAL]
---------------PhysicalCteConsumer ( cteId=CTEId#0 )
+------------PhysicalCteConsumer ( cteId=CTEId#0 )
 ------hashAgg[GLOBAL]
 --------hashAgg[GLOBAL]
-----------hashAgg[LOCAL]
-------------PhysicalCteConsumer ( cteId=CTEId#0 )
+----------PhysicalCteConsumer ( cteId=CTEId#0 )
 
 -- !has_grouping --
 PhysicalResultSink
diff --git 
a/regression-test/suites/statistics/test_full_analyze_hot_value.groovy 
b/regression-test/suites/statistics/test_full_analyze_hot_value.groovy
index 92211e69d05..f2e5031eb19 100644
--- a/regression-test/suites/statistics/test_full_analyze_hot_value.groovy
+++ b/regression-test/suites/statistics/test_full_analyze_hot_value.groovy
@@ -22,7 +22,7 @@ suite("test_full_analyze_hot_value") {
     sql """use test_full_analyze_hot_value"""
     sql """set global enable_auto_analyze=false"""
 
-    // Test 1: Full analyze collects hot_value for varchar column with skewed 
distribution
+    // Test 1: Full analyze does not collect hot_value by default.
     sql """drop table if exists full_hot_skew"""
     sql """CREATE TABLE full_hot_skew (
             key1 int NULL,
@@ -43,7 +43,20 @@ suite("test_full_analyze_hot_value") {
     logger.info("Test1 result: " + result)
     assertEquals(1, result.size())
     assertEquals("100.0", result[0][2])
-    // Full analyze should now collect hot_value
+    assertEquals("null", String.valueOf(result[0][17]))
+
+    result = sql """show column cached stats full_hot_skew(value1)"""
+    logger.info("Test1 cached result: " + result)
+    assertEquals(1, result.size())
+    assertEquals("null", String.valueOf(result[0][17]))
+
+    // Test 2: Full analyze collects hot_value when explicitly enabled.
+    sql """drop stats full_hot_skew"""
+    sql """analyze table full_hot_skew with sync with hot value"""
+    result = sql """show column stats full_hot_skew(value1)"""
+    logger.info("Test2 result: " + result)
+    assertEquals(1, result.size())
+    assertEquals("100.0", result[0][2])
     assertTrue(result[0][17].contains(":"), "Full analyze should collect 
hot_value, but got " + result[0][17])
     String[] hotValues = result[0][17].split(";")
     assertEquals(2, hotValues.length)
@@ -52,22 +65,22 @@ suite("test_full_analyze_hot_value") {
 
     // Verify cached stats also have hot_value
     result = sql """show column cached stats full_hot_skew(value1)"""
-    logger.info("Test1 cached result: " + result)
+    logger.info("Test2 cached result: " + result)
     assertEquals(1, result.size())
     hotValues = result[0][17].split(";")
     assertEquals(2, hotValues.length)
     assertTrue(hotValues[0].trim() == "'1':0.5" || hotValues[0].trim() == 
"'0':0.5")
     assertTrue(hotValues[1].trim() == "'1':0.5" || hotValues[1].trim() == 
"'0':0.5")
 
-    // Test 2: Full analyze collects hot_value for int column
+    // Test 3: Explicit full hot value collection works for int column.
     result = sql """show column stats full_hot_skew(key1)"""
-    logger.info("Test2 result: " + result)
+    logger.info("Test3 result: " + result)
     assertEquals(1, result.size())
     assertEquals("100.0", result[0][2])
     // key1 has 100 unique values, top 10 will each have proportion 0.01 -> 
ROUND to 0.01
     assertTrue(result[0][17].contains(":"), "Full analyze should collect 
hot_value for int column")
 
-    // Test 3: Full analyze with special characters in values
+    // Test 4: Explicit full hot value collection works with special 
characters in values.
     sql """drop table if exists full_hot_special"""
     sql """CREATE TABLE full_hot_special (
             key1 int NULL,
@@ -82,18 +95,18 @@ suite("test_full_analyze_hot_value") {
     """
     sql """insert into full_hot_special select number, " : ;a" from 
numbers("number"="100")"""
 
-    sql """analyze table full_hot_special with sync"""
+    sql """analyze table full_hot_special with sync with hot value"""
     result = sql """show column stats full_hot_special(value1)"""
-    logger.info("Test3 result: " + result)
+    logger.info("Test4 result: " + result)
     assertEquals(1, result.size())
     // All 100 rows have the same value " : ;a", so it should appear with 
ratio 1.0
     assertEquals("' : ;a':1.0", result[0][17])
 
-    // Test 4: Full analyze then sample analyze, hot_value should be updated
+    // Test 5: Sample analyze collects hot_value by default.
     sql """drop stats full_hot_skew"""
     sql """analyze table full_hot_skew with sample rows 400 with sync"""
     result = sql """show column stats full_hot_skew(value1)"""
-    logger.info("Test4 result: " + result)
+    logger.info("Test5 result: " + result)
     assertEquals(1, result.size())
     assertTrue(result[0][17].contains(":"), "Sample analyze should also 
collect hot_value")
     hotValues = result[0][17].split(";")
@@ -101,11 +114,17 @@ suite("test_full_analyze_hot_value") {
     assertTrue(hotValues[0].trim() == "'1':0.5" || hotValues[0].trim() == 
"'0':0.5")
     assertTrue(hotValues[1].trim() == "'1':0.5" || hotValues[1].trim() == 
"'0':0.5")
 
-    // Test 5: Verify full analyze produces same hot_value as sample analyze 
for same data
+    // Test 6: Sample analyze rejects WITH HOT VALUE because it always 
collects hot_value.
+    test {
+        sql """analyze table full_hot_skew with sample rows 400 with sync with 
hot value"""
+        exception "Sample analyze always collects hot value"
+    }
+
+    // Test 7: Explicit full analyze produces same hot_value as sample analyze 
for same data.
     sql """drop stats full_hot_skew"""
-    sql """analyze table full_hot_skew with sync"""
+    sql """analyze table full_hot_skew with sync with hot value"""
     def fullResult = sql """show column stats full_hot_skew(value1)"""
-    logger.info("Test5 full result: " + fullResult)
+    logger.info("Test7 full result: " + fullResult)
     assertEquals(1, fullResult.size())
     assertTrue(fullResult[0][17].contains(":"))
     def fullParts = fullResult[0][17].split(";").collect { it.trim() }.sort()
@@ -113,14 +132,14 @@ suite("test_full_analyze_hot_value") {
     sql """drop stats full_hot_skew"""
     sql """analyze table full_hot_skew with sample rows 40000 with sync"""
     def sampleResult = sql """show column stats full_hot_skew(value1)"""
-    logger.info("Test5 sample result: " + sampleResult)
+    logger.info("Test7 sample result: " + sampleResult)
     assertEquals(1, sampleResult.size())
     assertTrue(sampleResult[0][17].contains(":"))
     // Both full and sample should produce the same hot_value entries (order 
may differ)
     def sampleParts = sampleResult[0][17].split(";").collect { it.trim() 
}.sort()
     assertEquals(fullParts, sampleParts)
 
-    // Test 6: Full analyze on empty table should produce an empty hot_value 
string
+    // Test 8: Explicit full analyze on empty table should produce an empty 
hot_value string.
     sql """drop table if exists full_hot_empty"""
     sql """CREATE TABLE full_hot_empty (
             key1 int NULL,
@@ -133,14 +152,14 @@ suite("test_full_analyze_hot_value") {
             "replication_num" = "1"
         )
     """
-    sql """analyze table full_hot_empty with sync"""
+    sql """analyze table full_hot_empty with sync with hot value"""
     result = sql """show column stats full_hot_empty(value1)"""
-    logger.info("Test6 empty table result: " + result)
+    logger.info("Test8 empty table result: " + result)
     assertEquals(1, result.size())
     assertEquals("0.0", result[0][2])
     assertEquals("''", result[0][17])
 
-    // Test 7: Full analyze on all-NULL column should produce an empty 
hot_value string
+    // Test 9: Explicit full analyze on all-NULL column should produce an 
empty hot_value string.
     sql """drop table if exists full_hot_all_null"""
     sql """CREATE TABLE full_hot_all_null (
             key1 int NULL,
@@ -154,9 +173,9 @@ suite("test_full_analyze_hot_value") {
         )
     """
     sql """insert into full_hot_all_null select number, null from 
numbers("number"="100")"""
-    sql """analyze table full_hot_all_null with sync"""
+    sql """analyze table full_hot_all_null with sync with hot value"""
     result = sql """show column stats full_hot_all_null(value1)"""
-    logger.info("Test7 all-null result: " + result)
+    logger.info("Test9 all-null result: " + result)
     assertEquals(1, result.size())
     assertEquals("100.0", result[0][2])
     assertEquals("100.0", result[0][4])
diff --git a/regression-test/suites/statistics/test_hot_value.groovy 
b/regression-test/suites/statistics/test_hot_value.groovy
index 4d8ee5643df..8b62ff715ec 100644
--- a/regression-test/suites/statistics/test_hot_value.groovy
+++ b/regression-test/suites/statistics/test_hot_value.groovy
@@ -80,7 +80,7 @@ suite("test_hot_value") {
     sql """insert into test2 select number, " : ;a" from 
numbers("number"="10000")"""
     wait_row_count_reported("test_hot_value", "test1", 0, 4, "10000")
     wait_row_count_reported("test_hot_value", "test2", 0, 4, "10000")
-    sql """analyze table test1 with sync"""
+    sql """analyze table test1 with sync with hot value"""
     explain {
         sql("memo plan select * from test1")
         contains "hotValues=("


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to