public static boolean canBuildPlanUseTemplate(
      Analysis analysis,
      QueryStatement queryStatement,
      IPartitionFetcher partitionFetcher,
      ISchemaTree schemaTree,
      MPPQueryContext context,
      List<PartialPath> deviceList) {
    if (queryStatement.getGroupByComponent() != null
        || queryStatement.isSelectInto()
        || queryStatement.hasFill()
        || schemaTree.hasNormalTimeSeries()) {
      return false;
    }

    List<Template> templates = schemaTree.getUsingTemplates();
    if (templates.size() != 1 || templates.get(0) == null) {
      return false;
    }

    Template template = templates.get(0);

    if (queryStatement.isAggregationQuery()) {
      return canBuildAggregationPlanUseTemplate(
          analysis, queryStatement, partitionFetcher, schemaTree, context, template, deviceList);
    }

    List<Pair<Expression, String>> outputExpressions = new ArrayList<>();
    ColumnPaginationController paginationController =
        new ColumnPaginationController(
            queryStatement.getSeriesLimit(), queryStatement.getSeriesOffset());

    for (ResultColumn resultColumn : queryStatement.getSelectComponent().getResultColumns()) {
      Expression expression = resultColumn.getExpression();
      if ("*".equals(expression.getOutputSymbol())) {
        for (Map.Entry<String, IMeasurementSchema> entry : template.getSchemaMap().entrySet()) {
          if (paginationController.hasCurOffset()) {
            paginationController.consumeOffset();
          } else if (paginationController.hasCurLimit()) {
            String measurementName = entry.getKey();
            TimeSeriesOperand measurementPath =
                new TimeSeriesOperand(
                    new PartialPath(new String[] {measurementName}), entry.getValue().getType());
            // reserve memory for this expression
            context.reserveMemoryForFrontEnd(measurementPath.ramBytesUsed());
            outputExpressions.add(new Pair<>(measurementPath, null));
            paginationController.consumeLimit();
          } else {
            break;
          }
        }
        if (queryStatement.getSelectComponent().getResultColumns().size() == 1
            && queryStatement.getSeriesOffset() == 0
            && queryStatement.getSeriesLimit() == 0) {
          analysis.setTemplateWildCardQuery();
        }
      } else if (expression instanceof TimeSeriesOperand) {
        String measurementName = ((TimeSeriesOperand) expression).getPath().getMeasurement();
        if (template.getSchemaMap().containsKey(measurementName)) {
          if (paginationController.hasCurOffset()) {
            paginationController.consumeOffset();
          } else if (paginationController.hasCurLimit()) {
            TimeSeriesOperand measurementPath =
                new TimeSeriesOperand(
                    new PartialPath(new String[] {measurementName}),
                    template.getSchemaMap().get(measurementName).getType());
            // reserve memory for this expression
            context.reserveMemoryForFrontEnd(measurementPath.ramBytesUsed());
            outputExpressions.add(new Pair<>(measurementPath, resultColumn.getAlias()));
          } else {
            break;
          }
        }
      } else {
        return false;
      }
    }

    if (queryStatement.hasOrderByExpression()) {
      return false;
    }

    analyzeSelect(queryStatement, analysis, outputExpressions, template);

    analyzeDeviceToWhere(analysis, queryStatement);
    if (analysis.getWhereExpression() != null
        && analysis.getWhereExpression().equals(ConstantOperand.FALSE)) {
      analyzeOutput(analysis, queryStatement, outputExpressions);
      analysis.setFinishQueryAfterAnalyze(true);
      return true;
    }

    if (deviceList.isEmpty()) {
      analysis.setFinishQueryAfterAnalyze(true);
      return true;
    }
    analysis.setDeviceList(deviceList);

    analyzeDeviceToOrderBy(analysis, queryStatement, schemaTree, deviceList, context);
    analyzeDeviceToSourceTransform(analysis);
    analyzeDeviceToSource(analysis);

    analyzeDeviceViewOutput(analysis, queryStatement);

    analyzeFill(analysis, queryStatement);

    // generate result set header according to output expressions
    analyzeOutput(analysis, queryStatement, outputExpressions);

    context.generateGlobalTimeFilter(analysis);
    // fetch partition information
    analyzeDataPartition(analysis, schemaTree, partitionFetcher, context);
    return true;
  }
examine that if all devices are in same template, if true, use the TemplatedAnalyze,
TemplatedLogicalPlan, TemplatedLogicalPlanBuilder to optimize it.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TemplatedAnalyze.java