private List<PlanNode> constructDeviceTableScanByTags(
      final DeviceTableScanNode node, final PlanContext context) {
    DataPartition dataPartition = analysis.getDataPartitionInfo();
    if (dataPartition == null) {
      node.setRegionReplicaSet(NOT_ASSIGNED);
      return Collections.singletonList(node);
    }

    String dbName = node.getQualifiedObjectName().getDatabaseName();
    Map<TSeriesPartitionSlot, Map<TTimePartitionSlot, List<TRegionReplicaSet>>> seriesSlotMap =
        dataPartition.getDataPartitionMap().get(dbName);
    if (seriesSlotMap == null) {
      throw new SemanticException(
          String.format("Given queried database: %s is not exist!", dbName));
    }
    Map<Integer, List<TRegionReplicaSet>> cachedSeriesSlotWithRegions = new HashMap<>();

    List<DeviceEntry> crossRegionDevices = new ArrayList<>();

    final Map<TRegionReplicaSet, DeviceTableScanNode> tableScanNodeMap = new HashMap<>();
    final Map<TRegionReplicaSet, Integer> regionDeviceCount = new HashMap<>();
    for (final DeviceEntry deviceEntry : node.getDeviceEntries()) {
      final List<TRegionReplicaSet> regionReplicaSets =
          getDeviceReplicaSets(
              dataPartition,
              seriesSlotMap,
              deviceEntry.getDeviceID(),
              node.getTimeFilter(),
              cachedSeriesSlotWithRegions);
      regionReplicaSets.forEach(
          regionReplicaSet ->
              regionDeviceCount.put(
                  regionReplicaSet, regionDeviceCount.getOrDefault(regionReplicaSet, 0) + 1));
      if (regionReplicaSets.size() != 1) {
        crossRegionDevices.add(deviceEntry);
        continue;
      }
      final DeviceTableScanNode deviceTableScanNode =
          tableScanNodeMap.computeIfAbsent(
              regionReplicaSets.get(0),
              k -> {
                final DeviceTableScanNode scanNode =
                    new DeviceTableScanNode(
                        queryId.genPlanNodeId(),
                        node.getQualifiedObjectName(),
                        node.getOutputSymbols(),
                        node.getAssignments(),
                        new ArrayList<>(),
                        node.getTagAndAttributeIndexMap(),
                        node.getScanOrder(),
                        node.getTimePredicate().orElse(null),
                        node.getPushDownPredicate(),
                        node.getPushDownLimit(),
                        node.getPushDownOffset(),
                        node.isPushLimitToEachDevice(),
                        node.containsNonAlignedDevice());
                scanNode.setRegionReplicaSet(regionReplicaSets.get(0));
                return scanNode;
              });
      deviceTableScanNode.appendDeviceEntry(deviceEntry);
    }
    List<PlanNode> result = new ArrayList<>(tableScanNodeMap.values());
    if (context.hasSortProperty) {
      processSortProperty(node, result, context);
    }
    context.mostUsedRegion =
        regionDeviceCount.entrySet().stream()
            .max(Comparator.comparingInt(Map.Entry::getValue))
            .map(Map.Entry::getKey)
            .orElse(null);
    if (!crossRegionDevices.isEmpty()) {
      node.setDeviceEntries(crossRegionDevices);
      MergeSortNode mergeSortNode =
          new MergeSortNode(
              queryId.genPlanNodeId(), context.expectedOrderingScheme, node.getOutputSymbols());
      for (PlanNode node1 : constructDeviceTableScanByRegionReplicaSet(node, context)) {
        mergeSortNode.addChild(node1);
      }
      nodeOrderingMap.put(mergeSortNode.getPlanNodeId(), mergeSortNode.getOrderingScheme());
      result.add(mergeSortNode);
    }
    return result;
  }
This class is used to generate distributed plan for table model.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/distribute/TableDistributedPlanGenerator.java