Jelajahi Sumber

pms 保养计划明细 查询保养项的累计运行时长 累计运行公里数 逻辑 运行记录模板包含多个累计类型属性

zhangcl 1 bulan lalu
induk
melakukan
b78f42e988

+ 1 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmaintenancebom/IotMaintenanceBomController.java

@@ -170,7 +170,7 @@ public class IotMaintenanceBomController {
             // 获取设备ID对应的累积类型的属性集合
             List<IotModelTemplateAttrsDO> deviceAttrs = deviceAttrsPair.get(bomVO.getDeviceId());
             if (CollUtil.isNotEmpty(deviceAttrs) && deviceAttrs.size() > 2) {
-                // 模板属性中有多个 累计运行时长 或 累计运行公里数 属性 需要使用另外的方式赋值
+                // 模板属性中有多个 累计运行时长 或 累计运行公里数 属性 需要使用另外的方式赋值(保养计划配置时选择)
             } else {
                 MapUtils.findAndThen(deviceRunLogMap, bomVO.getDeviceId(),
                         device -> bomVO.setTotalMileage(device.getTotalMileage()));

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmodeltemplateattrs/vo/IotModelTemplateAttrsPageReqVO.java

@@ -84,4 +84,7 @@ public class IotModelTemplateAttrsPageReqVO extends PageParam {
 
     @Schema(description = "编码集合", example = "sc,gls")
     private List<String> codes;
+
+    @Schema(description = "多个累计类型属性标识", example = "Y多个累计类型属性")
+    private String multipleFlag;
 }

+ 9 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmodeltemplateattrs/vo/IotModelTemplateAttrsRespVO.java

@@ -7,8 +7,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
+import java.math.BigDecimal;
 import java.time.LocalDate;
-import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
 
@@ -98,4 +98,12 @@ public class IotModelTemplateAttrsRespVO {
 
     private String deviceCode;
 
+    /**
+     * 扩展属性
+     */
+    @Schema(description = "累计运行时长", example = "2398.2")
+    private BigDecimal totalRunTime;
+
+    @Schema(description = "累计运行公里数", example = "2398.2")
+    private BigDecimal totalMileage;
 }

+ 14 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/vo/IotDeviceRespVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pms.controller.admin.vo;
 
+import cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -7,6 +8,7 @@ import lombok.Data;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "管理后台 - 设备台账 Response VO")
 @Data
@@ -152,8 +154,10 @@ public class IotDeviceRespVO {
     private String bomNodeId;
     @Schema(description = "bom节点名称")
     private String name;
-    @Schema(description = "bom节点编码")
+    @Schema(description = "bom节点编码 累计运行时长属性名称")
     private String code;
+    @Schema(description = "bom节点类型 累计运行公里数属性名称")
+    private String type;
     @Schema(description = "设备分类BOM同步状态 1已同步 0未同步")
     private Integer bomSyncStatus;
 
@@ -191,4 +195,13 @@ public class IotDeviceRespVO {
     @Schema(description = "纬度")
     private Double lat;
     private String location;
+
+    /**
+     * 扩展属性
+     */
+    @Schema(description = "模板中涉及多个累计运转时长的属性集合")
+    private List<IotDeviceRunLogRespVO> timeAccumulatedAttrs;
+
+    @Schema(description = "模板中涉及多个累计运行公里数的属性集合")
+    private List<IotDeviceRunLogRespVO> mileageAccumulatedAttrs;
 }

+ 13 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotdevicerunlog/IotDeviceRunLogMapper.java

@@ -41,5 +41,18 @@ public interface IotDeviceRunLogMapper extends BaseMapperX<IotDeviceRunLogDO> {
                 .orderByDesc(IotDeviceRunLogDO::getId));
     }
 
+    /**
+     * 查询指定设备 运行记录模板属性对应的累计时长 累计里程
+     * @param deviceIds
+     * @return
+     */
     List<IotDeviceRunLogRespVO> distinctDevices(@Param("deviceIds") Collection<Long> deviceIds);
+
+    /**
+     * 查询指定设备 运行记录模板属性对应的累计时长 累计里程
+     * @param deviceIds
+     * @return
+     */
+    List<IotDeviceRunLogRespVO> multipleAccumulatedData(@Param("deviceIds") Collection<Long> deviceIds,
+                                                        @Param("attrNames") Collection<String> attrNames);
 }

+ 17 - 5
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotmodeltemplateattrs/IotModelTemplateAttrsMapper.java

@@ -45,14 +45,15 @@ public interface IotModelTemplateAttrsMapper extends BaseMapperX<IotModelTemplat
     }
 
     default List<IotModelTemplateAttrsDO> selectList(IotModelTemplateAttrsPageReqVO reqVO) {
-        return selectList(new LambdaQueryWrapperX<IotModelTemplateAttrsDO>()
+        LambdaQueryWrapperX<IotModelTemplateAttrsDO> queryWrapper = new LambdaQueryWrapperX<>();
+
+        // 添加基础查询条件
+        queryWrapper
                 .eqIfPresent(IotModelTemplateAttrsDO::getDeviceCategoryId, reqVO.getDeviceCategoryId())
                 .inIfPresent(IotModelTemplateAttrsDO::getDeviceCategoryId, reqVO.getDeviceCategoryIds())
                 .eqIfPresent(IotModelTemplateAttrsDO::getDeviceId, reqVO.getDeviceId())
                 .eqIfPresent(IotModelTemplateAttrsDO::getTemplateId, reqVO.getTemplateId())
                 .likeIfPresent(IotModelTemplateAttrsDO::getName, reqVO.getName())
-                .eqIfPresent(IotModelTemplateAttrsDO::getCode, reqVO.getCode())
-                .inIfPresent(IotModelTemplateAttrsDO::getCode, reqVO.getCodes())
                 .eqIfPresent(IotModelTemplateAttrsDO::getType, reqVO.getType())
                 .eqIfPresent(IotModelTemplateAttrsDO::getRequiredFlag, reqVO.getRequiredFlag())
                 .eqIfPresent(IotModelTemplateAttrsDO::getDefaultValue, reqVO.getDefaultValue())
@@ -66,8 +67,19 @@ public interface IotModelTemplateAttrsMapper extends BaseMapperX<IotModelTemplat
                 .eqIfPresent(IotModelTemplateAttrsDO::getSort, reqVO.getSort())
                 .eqIfPresent(IotModelTemplateAttrsDO::getSelectOptions, reqVO.getSelectOptions())
                 .eqIfPresent(IotModelTemplateAttrsDO::getVersion, reqVO.getVersion())
-                .betweenIfPresent(IotModelTemplateAttrsDO::getCreateTime, reqVO.getCreateTime())
-        );
+                .betweenIfPresent(IotModelTemplateAttrsDO::getCreateTime, reqVO.getCreateTime());
+
+        // 处理 multipleFlag 和 code 的特殊逻辑
+        if ("Y".equals(reqVO.getMultipleFlag())) {
+            // 查询 code 为 NULL 或 '' 的记录
+            queryWrapper.and(wrapper -> wrapper.isNull(IotModelTemplateAttrsDO::getCode).or().eq(IotModelTemplateAttrsDO::getCode, ""));
+        } else {
+            // 正常查询 code 条件
+            queryWrapper.eqIfPresent(IotModelTemplateAttrsDO::getCode, reqVO.getCode())
+                    .inIfPresent(IotModelTemplateAttrsDO::getCode, reqVO.getCodes());
+        }
+
+        return selectList(queryWrapper);
     }
 
     @DS("yanfan")

+ 115 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/IotDeviceServiceImpl.java

@@ -11,6 +11,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
 import cn.iocoder.yudao.module.pms.ThingsModelDTO;
 import cn.iocoder.yudao.module.pms.controller.admin.TableDataInfo;
+import cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmodeltemplateattrs.vo.IotModelTemplateAttrsPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDevicePageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceSaveReqVO;
@@ -18,13 +20,17 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.IotInfoClassifyDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.IotTreeDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmodel.IotModelDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotmodeltemplateattrs.IotModelTemplateAttrsDO;
 import cn.iocoder.yudao.module.pms.dal.mysql.IotDeviceMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.IotInfoClassifyMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.IotTreeMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.TDDeviceMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.iotdevicerunlog.IotDeviceRunLogMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmodel.IotModelMapper;
 import cn.iocoder.yudao.module.pms.service.iotbom.IotBomService;
 import cn.iocoder.yudao.module.pms.service.iotdevicebom.IotDeviceBomService;
+import cn.iocoder.yudao.module.pms.service.iotdevicerunlog.IotDeviceRunLogService;
+import cn.iocoder.yudao.module.pms.service.iotmodeltemplateattrs.IotModelTemplateAttrsService;
 import cn.iocoder.yudao.module.pms.service.yanfan.ThingsModelService;
 import cn.iocoder.yudao.module.pms.service.yanfan.YfDeviceService;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
@@ -85,6 +91,12 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     private String yanfanUrl;
     @Autowired
     private IotDeviceBomService iotDeviceBomService;
+    @Resource
+    private IotModelTemplateAttrsService iotModelTemplateAttrsService;
+    @Resource
+    private IotDeviceRunLogService iotDeviceRunLogService;
+    @Resource
+    private IotDeviceRunLogMapper iotDeviceRunLogMapper;
 
     @Override
     public List<IotDeviceDO> getMapDevice() {
@@ -357,21 +369,124 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     @Override
     public List<IotDeviceRespVO> deviceAssociateBomList(IotDevicePageReqVO pageReqVO) {
         List<IotDeviceRespVO> devices = iotDeviceMapper.deviceAssociateBomList(pageReqVO.getDeviceIds(), pageReqVO.getBomFlag());
+        // 调整 累计运行时间 累计运行里程 的逻辑 模板属性中涉及多个累计运行时长 累计运行里程 的 这里不需要赋值
+        // 查询所有设备的设备分类
+        // 查询设备分类对应的运行记录模板中的多个累计类型的属性列表(匹配相应的累计运行时长 累计运行里程的值)
+        Set<Long> deviceCategoryIds = new HashSet<>();
+        // 设备id集合 根据设备id查询所有正常的 累计时长 累计公里数
+        Set<Long> deviceIds = new HashSet<>();
+        // 运行记录模板中涉及多个累计时长 公里数属性的设备id集合
+        Set<Long> multipleAttrDeviceIds = new HashSet<>();
+        // 运行记录模板中涉及多个累计时长 公里数属性名称集合
+        Set<String> multipleAttrNames = new HashSet<>();
+        // 设备对应的设备分类 key设备id   value设备分类id
+        Map<Long, Long> deviceCategoryPair = new HashMap<>();
+        // k设备id   v设备分类包含的模板中与累计运行时长、累计运行公里数相关的属性集合
+        Map<Long, List<IotModelTemplateAttrsDO>> deviceAttrsPair = new HashMap<>();
         // 查询所有 设备boms节点的所有上级节点名称 拼接出 全路径 的bom节点
         List<Long> bomNodeIds = new ArrayList<>();
+        // key设备id  value设备对应的运行记录模板中涉及多个累计时长累计公里数的属性集合
+        Map<Long, List<IotDeviceRunLogRespVO>> deviceRunLogPair = new HashMap<>();
         if (CollUtil.isNotEmpty(devices)) {
+            // 设备运行模板中正常的 累计时长 累计里程 集合
+            Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = new HashMap<>();
             devices.forEach(device -> {
+                deviceIds.add(device.getId());
                 bomNodeIds.add(Long.valueOf(device.getBomNodeId()));
+                deviceCategoryIds.add(device.getAssetClass());
+                deviceCategoryPair.put(device.getId(), device.getAssetClass());
             });
+
+            if (CollUtil.isNotEmpty(deviceCategoryIds)) {
+                // 查询设备分类id集合对应的运行记录模板中涉及 累计运行时长、累计运行公里数 的属性
+                IotModelTemplateAttrsPageReqVO reqVO = new IotModelTemplateAttrsPageReqVO();
+                reqVO.setMultipleFlag("Y");
+                reqVO.setDeviceCategoryIds(new ArrayList<>(deviceCategoryIds));
+                reqVO.setIsSum(1);  // 累计属性标识
+                List<IotModelTemplateAttrsDO> accumulatedAttrs = iotModelTemplateAttrsService.getIotDeviceTemplateAttrs(reqVO);
+                // k设备分类id   v设备分类包含的模板中 多个 累计运行时长 累计运行公里数相关的属性集合
+                Map<Long, List<IotModelTemplateAttrsDO>> accumulatedAttrPair = new HashMap<>();
+                if (CollUtil.isNotEmpty(accumulatedAttrs)) {
+                    accumulatedAttrs.forEach(attr -> {
+                        if (accumulatedAttrPair.containsKey(attr.getDeviceCategoryId())) {
+                            List<IotModelTemplateAttrsDO> tempAttrs = accumulatedAttrPair.get(attr.getDeviceCategoryId());
+                            tempAttrs.add(attr);
+                            accumulatedAttrPair.put(attr.getDeviceCategoryId(), tempAttrs);
+                        } else {
+                            List<IotModelTemplateAttrsDO> tempAttrs = new ArrayList<>();
+                            tempAttrs.add(attr);
+                            accumulatedAttrPair.put(attr.getDeviceCategoryId(), tempAttrs);
+                        }
+                        multipleAttrNames.add(attr.getName());
+                    });
+                }
+                if (CollUtil.isNotEmpty(accumulatedAttrPair)) {
+                    accumulatedAttrPair.forEach((k,v) -> {
+                        // k设备分类id   v设备分类包含的模板中 多个累计运行时长 累计运行公里数 相关的属性集合
+                        deviceCategoryPair.forEach((ik, iv) -> {
+                            // ik设备id   iv设备分类id
+                            if (iv.equals(k)) {
+                                // key设备id  value设备分类包含的模板中 多个累计运行时长 累计运行公里数 相关的属性集合
+                                deviceAttrsPair.put(ik, v);
+                                multipleAttrDeviceIds.add(ik);
+                            }
+                        });
+                    });
+                }
+                // 根据设备id集合查询正常的 累计时长 累计公里数
+                deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
+
+                // 根据设备id 运行记录模板属性名称 查询对应的 累计运行时长 累计运行公里数
+                if (CollUtil.isNotEmpty(multipleAttrDeviceIds) && CollUtil.isNotEmpty(multipleAttrNames)) {
+                    List<IotDeviceRunLogRespVO> accumulatedData = iotDeviceRunLogMapper.multipleAccumulatedData(multipleAttrDeviceIds, multipleAttrNames);
+                    // 组装每个设备对应的属性 累计时长 累计里程 集合
+                    if (CollUtil.isNotEmpty(accumulatedData)) {
+                        accumulatedData.forEach(data -> {
+                            if (deviceRunLogPair.containsKey(data.getDeviceId())) {
+                                List<IotDeviceRunLogRespVO> tempRunLogs = deviceRunLogPair.get(data.getDeviceId());
+                                tempRunLogs.add(data);
+                                deviceRunLogPair.put(data.getDeviceId(), tempRunLogs);
+                            } else {
+                                List<IotDeviceRunLogRespVO> tempRunLogs = new ArrayList<>();
+                                tempRunLogs.add(data);
+                                deviceRunLogPair.put(data.getDeviceId(), tempRunLogs);
+                            }
+                        });
+                    }
+                }
+            }
+
             // 查询 bomNodeIds 集合中每个bom节点的所有上级节点
             Map<Long, String> bomFullPaths = iotDeviceBomService.buildBomFullPaths(bomNodeIds);
+            Map<Long, IotDeviceRunLogRespVO> finalDeviceRunLogMap = deviceRunLogMap;
             devices.forEach(device -> {
                 if (bomFullPaths.containsKey(Long.valueOf(device.getBomNodeId()))) {
                     if (StrUtil.isNotBlank(bomFullPaths.get(Long.valueOf(device.getBomNodeId())))) {
                         device.setName(bomFullPaths.get(Long.valueOf(device.getBomNodeId())));
                     }
                 }
+                if (deviceRunLogPair.containsKey(device.getId())) {
+                    // 区分开 时间 里程 的累计属性列表 便于前端选择
+                    List<IotDeviceRunLogRespVO> mileageRunLogs = new ArrayList<>();
+                    List<IotDeviceRunLogRespVO> timeRunLogs = deviceRunLogPair.get(device.getId());
+                    timeRunLogs.forEach(log -> {
+                        log.setType(1);
+                        IotDeviceRunLogRespVO tempRunLog = new IotDeviceRunLogRespVO();
+                        BeanUtils.copyProperties(log, tempRunLog);
+                        tempRunLog.setType(2);
+                        mileageRunLogs.add(tempRunLog);
+                    });
+                    device.setTimeAccumulatedAttrs(timeRunLogs);
+                    device.setMileageAccumulatedAttrs(mileageRunLogs);
+                }
+                // 这里只赋值正常的 累计时长 累计公里数 多个累计属性的需要在保养配置中选择
+                if (CollUtil.isNotEmpty(finalDeviceRunLogMap) && finalDeviceRunLogMap.containsKey(device.getId())) {
+                    IotDeviceRunLogRespVO tempRunLog = finalDeviceRunLogMap.get(device.getId());
+                    device.setTotalRunTime(tempRunLog.getTotalRunTime());
+                    device.setTotalMileage(tempRunLog.getTotalMileage());
+                }
             });
+
         }
         return devices;
     }

+ 36 - 48
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotDeviceMapper.xml

@@ -360,54 +360,42 @@
 
     <select id="deviceAssociateBomList"
             resultType="cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO">
-        SELECT *
-        FROM (
-            SELECT
-                t.id ,
-                t.device_code,
-                t.device_name,
-                t.asset_class,
-                t.brand,
-                t.model,
-                t.dept_id,
-                t.device_status,
-                t.asset_property,
-                t.pic_url,
-                t.remark,
-                t.create_time,
-                t1.id bomNodeId,
-                t1.`name`,
-                t1.`code`
-            FROM
-                rq_iot_device t
-            INNER JOIN rq_iot_device_bom t1 ON ( t1.device_id = t.id AND t1.deleted = 0 )
-            WHERE
-                t.deleted = 0
-            AND t1.deleted = 0
-            AND t1.leaf_flag = 1
-            <if test='bomFlag!=null and bomFlag!="" and bomFlag=="b" '>
-                AND t1.type LIKE '%2%'
-            </if>
-            <if test='bomFlag!=null and bomFlag!="" and bomFlag=="w" '>
-                AND t1.type LIKE '%1%'
-            </if>
-            <if test="deviceIds != null and deviceIds.size &gt; 0">
-                AND t.id IN
-                <foreach collection="deviceIds" index="index" item="key" open="(" separator="," close=")">
-                    #{key}
-                </foreach>
-            </if>
-        ) t2
-        LEFT JOIN (
-            SELECT
-                device_id,
-                MAX(total_run_time) AS total_run_time,
-                MAX(total_mileage) AS total_mileage
-            FROM rq_iot_device_run_log
-            WHERE deleted = 0
-            AND device_id IS NOT NULL
-            GROUP BY device_id
-        ) tmp ON tmp.device_id = t2.id
+        SELECT
+            t.id ,
+            t.device_code,
+            t.device_name,
+            t.asset_class,
+            t.brand,
+            t.model,
+            t.dept_id,
+            t.device_status,
+            t.asset_property,
+            t.pic_url,
+            t.remark,
+            t.create_time,
+            t1.id bomNodeId,
+            t1.`name`,
+            t1.`code`,
+            t1.`type`
+        FROM
+            rq_iot_device t
+        INNER JOIN rq_iot_device_bom t1 ON ( t1.device_id = t.id AND t1.deleted = 0 )
+        WHERE
+            t.deleted = 0
+        AND t1.deleted = 0
+        AND t1.leaf_flag = 1
+        <if test='bomFlag!=null and bomFlag!="" and bomFlag=="b" '>
+            AND t1.type LIKE '%2%'
+        </if>
+        <if test='bomFlag!=null and bomFlag!="" and bomFlag=="w" '>
+            AND t1.type LIKE '%1%'
+        </if>
+        <if test="deviceIds != null and deviceIds.size &gt; 0">
+            AND t.id IN
+            <foreach collection="deviceIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
     </select>
     <select id="deviceAssociateBomListPage"
             resultType="cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO">

+ 29 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotDeviceRunLogMapper.xml

@@ -28,4 +28,33 @@
         GROUP BY drl.device_id, mta.`code`
     </select>
 
+    <!-- 查询指定设备 指定运行模板属性的累计时长 累计公里数 -->
+    <select id="multipleAccumulatedData"
+            resultType="cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO">
+        SELECT
+            device_id,
+            point_name,
+            MAX( total_run_time ) AS total_run_time,
+            MAX( total_mileage ) AS total_mileage
+        FROM
+            rq_iot_device_run_log
+        WHERE
+            deleted = 0
+            <if test="deviceIds != null and deviceIds.size &gt; 0">
+                AND device_id IN
+                <foreach collection="deviceIds" index="index" item="key" open="(" separator="," close=")">
+                    #{key}
+                </foreach>
+            </if>
+            <if test="attrNames != null and attrNames.size &gt; 0">
+                AND point_name IN
+                <foreach collection="attrNames" index="index" item="key" open="(" separator="," close=")">
+                    #{key}
+                </foreach>
+            </if>
+        GROUP BY
+            device_id,
+            point_name
+    </select>
+
 </mapper>