You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1091 lines
48 KiB
XML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.ems.report.mapper.TempBoardMapper">
<!-- 公共字段片段:每个分表 SELECT 温度相关字段 + JOIN 测点名称 -->
<sql id="baseColumns">
t.monitorId,
t.temperature,
t.collectTime,
t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
</sql>
<!-- 公共 JOIN关联测点主信息表获取 monitor_name 和 monitor_type -->
<sql id="baseJoin">
LEFT JOIN ems_base_monitor_info ebmi ON t.monitorId = ebmi.monitor_code
</sql>
<!-- 公共过滤温度类型设备type=5 温度, type=6 温湿度),且温度 > 0 -->
<sql id="tempFilter">
AND (ebmi.monitor_type IN (5, 6) OR ebmi.monitor_type IS NULL)
AND (t.temperature IS NULL OR (t.temperature BETWEEN 0 AND 79))
AND (
(ebmi.monitor_type = 5 AND t.temperature > 0) OR
(ebmi.monitor_type = 6 AND t.temperature > 0) OR
(ebmi.monitor_type NOT IN (5, 6) OR ebmi.monitor_type IS NULL)
)
</sql>
<!-- 公共时间过滤 -->
<sql id="timeFilter">
AND t.collectTime >= #{startTime}
AND t.collectTime &lt; #{endTime}
</sql>
<!-- ==================== A. 温度总览 ==================== -->
<!-- A1. 活跃测点数 -->
<select id="countDistinctMonitors" resultType="int">
SELECT COUNT(DISTINCT sub.monitorId)
FROM (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
) sub
</select>
<!-- A2. 最新平均温度CTE + ROW_NUMBER 取每个测点最新一条) -->
<select id="selectLatestAvgTemp" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardOverviewVo">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT ROUND(AVG(temperature), 2) AS avgLatestTemp
FROM latest WHERE rn = 1
</select>
<!-- A3. 最新最高/最低温度(先取每个测点最新值,再用独立 CTE 算极值,避免聚合函数嵌套子查询) -->
<select id="selectLatestMinMaxTemp" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardOverviewVo">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
),
latest_only AS (
SELECT monitorId, temperature, monitorName
FROM latest WHERE rn = 1
),
max_val AS (
SELECT TOP 1 monitorId AS maxTempMonitorId, temperature AS maxLatestTemp
FROM latest_only ORDER BY temperature DESC
),
min_val AS (
SELECT TOP 1 monitorId AS minTempMonitorId, temperature AS minLatestTemp
FROM latest_only ORDER BY temperature ASC
)
SELECT m.maxTempMonitorId, m.maxLatestTemp,
n.minTempMonitorId, n.minLatestTemp
FROM max_val m CROSS JOIN min_val n
</select>
<!-- A4. 高温 TopN -->
<select id="selectHighTempTopN" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardOverviewVo$MonitorTempRank">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT TOP (#{topN}) monitorId, monitorName, temperature, collectTime
FROM latest WHERE rn = 1
ORDER BY temperature DESC
</select>
<!-- A5. 低温 TopN -->
<select id="selectLowTempTopN" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardOverviewVo$MonitorTempRank">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT TOP (#{topN}) monitorId, monitorName, temperature, collectTime
FROM latest WHERE rn = 1
ORDER BY temperature ASC
</select>
<!-- A6. 数据新鲜度概览 -->
<select id="selectFreshnessList" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardOverviewVo$FreshnessItem">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT monitorId, monitorName, temperature, collectTime,
DATEDIFF(SECOND, collectTime, GETUTCDATE()) AS ageSeconds
FROM latest WHERE rn = 1
ORDER BY ageSeconds DESC
</select>
<!-- ==================== B. 实时监控 ==================== -->
<!-- B1. 实时温度明细(含延迟) -->
<select id="selectRealtimeDetail" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardRealtimeVo">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, recodeTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT monitorId, monitorName, temperature, collectTime, recodeTime,
DATEDIFF(SECOND, collectTime, recodeTime) AS delaySeconds,
DATEDIFF(SECOND, collectTime, GETUTCDATE()) AS staleSeconds
FROM latest WHERE rn = 1
ORDER BY collectTime DESC, monitorId
</select>
<!-- B2. 高温测点 -->
<select id="selectHighTempMonitors" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardRealtimeVo">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, recodeTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT monitorId, monitorName, temperature, collectTime, recodeTime,
DATEDIFF(SECOND, collectTime, recodeTime) AS delaySeconds
FROM latest WHERE rn = 1 AND temperature >= #{highTempThreshold}
ORDER BY temperature DESC
</select>
<!-- B3. 低温测点 -->
<select id="selectLowTempMonitors" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardRealtimeVo">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, recodeTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT monitorId, monitorName, temperature, collectTime, recodeTime,
DATEDIFF(SECOND, collectTime, recodeTime) AS delaySeconds
FROM latest WHERE rn = 1 AND temperature &lt;= #{lowTempThreshold}
ORDER BY temperature ASC
</select>
<!-- B4. 长时间未更新测点 -->
<select id="selectStaleMonitors" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardRealtimeVo">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, recodeTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT monitorId, monitorName, temperature, collectTime,
DATEDIFF(SECOND, collectTime, GETUTCDATE()) AS staleSeconds
FROM latest WHERE rn = 1
AND DATEDIFF(SECOND, collectTime, GETUTCDATE()) >= #{staleThreshold}
ORDER BY staleSeconds DESC
</select>
<!-- B5. 入库延迟排行 -->
<select id="selectDelayRanking" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardRealtimeVo">
WITH all_data AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime, t.recodeTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
latest AS (
SELECT monitorId, temperature, collectTime, recodeTime, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime DESC, recodeTime DESC) AS rn
FROM all_data
)
SELECT monitorId, monitorName, collectTime, recodeTime,
DATEDIFF(SECOND, collectTime, recodeTime) AS delaySeconds
FROM latest WHERE rn = 1
ORDER BY delaySeconds DESC
</select>
<!-- ==================== C. 趋势分析 ==================== -->
<!-- C1. 单测点分钟趋势 -->
<select id="selectMinuteTrend" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardTrendVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
FORMAT(t.collectTime, 'yyyy-MM-dd HH:mm:00') AS statTime,
t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp,
COUNT(*) AS sampleCount
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
t.monitorId = #{monitorId}
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY FORMAT(t.collectTime, 'yyyy-MM-dd HH:mm:00'), t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY statTime
</select>
<!-- C2. 单测点小时趋势 -->
<select id="selectHourlyTrend" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardTrendVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
FORMAT(t.collectTime, 'yyyy-MM-dd HH:00:00') AS statTime,
t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp,
ROUND(MAX(t.temperature), 2) AS maxTemp,
ROUND(MIN(t.temperature), 2) AS minTemp,
COUNT(*) AS sampleCount
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
t.monitorId = #{monitorId}
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY FORMAT(t.collectTime, 'yyyy-MM-dd HH:00:00'), t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY statTime
</select>
<!-- C3. 多测点对比趋势 -->
<select id="selectMultiCompareTrend" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardTrendVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
FORMAT(t.collectTime, 'yyyy-MM-dd HH:mm:00') AS statTime,
t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<if test="monitorIds != null and monitorIds.size() > 0">
AND t.monitorId IN
<foreach collection="monitorIds" item="mid" open="(" separator="," close=")">
#{mid}
</foreach>
</if>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY FORMAT(t.collectTime, 'yyyy-MM-dd HH:mm:00'), t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY statTime, monitorId
</select>
<!-- C4. 日均温趋势 -->
<select id="selectDailyTrend" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardTrendVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
FORMAT(CAST(t.collectTime AS DATE), 'yyyy-MM-dd') AS statTime,
NULL AS monitorId,
NULL AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp,
ROUND(MAX(t.temperature), 2) AS maxTemp,
ROUND(MIN(t.temperature), 2) AS minTemp,
COUNT(*) AS sampleCount
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY CAST(t.collectTime AS DATE)
</foreach>
ORDER BY statTime
</select>
<!-- C5. 温度变化率趋势 -->
<select id="selectChangeRateTrend" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardTrendVo">
WITH base AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<if test="monitorId != null and monitorId != ''">
AND t.monitorId = #{monitorId}
</if>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
seq AS (
SELECT monitorId, collectTime, temperature,
LAG(collectTime) OVER (PARTITION BY monitorId ORDER BY collectTime) AS prevTime,
LAG(temperature) OVER (PARTITION BY monitorId ORDER BY collectTime) AS prevTemp
FROM base
)
SELECT monitorId,
collectTime AS statTime,
prevTime,
prevTemp,
temperature,
ROUND(
CAST(temperature - prevTemp AS FLOAT) /
NULLIF(CAST(DATEDIFF(SECOND, prevTime, collectTime) AS FLOAT), 0) * 60,
4
) AS changeRate
FROM seq WHERE prevTime IS NOT NULL
ORDER BY collectTime
</select>
<!-- C6. 峰谷时刻表 -->
<select id="selectPeakValley" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardTrendVo">
WITH base AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
ranked AS (
SELECT monitorId, collectTime, temperature, monitorName,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY temperature DESC, collectTime ASC) AS rnMax,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY temperature ASC, collectTime ASC) AS rnMin
FROM base
)
SELECT monitorId, monitorName,
MAX(CASE WHEN rnMax = 1 THEN temperature END) AS peakTemp,
MAX(CASE WHEN rnMax = 1 THEN collectTime END) AS peakTime,
MAX(CASE WHEN rnMin = 1 THEN temperature END) AS valleyTemp,
MAX(CASE WHEN rnMin = 1 THEN collectTime END) AS valleyTime
FROM ranked
GROUP BY monitorId, monitorName
</select>
<!-- ==================== D. 分布分析 ==================== -->
<!-- D1. 温度区间分布 -->
<select id="selectIntervalDistribution" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardDistributionVo">
SELECT sub.tempBucket, COUNT(*) AS sampleCount
FROM (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.temperature,
CASE
WHEN t.temperature &lt; 15 THEN '&lt;15'
WHEN t.temperature &lt; 20 THEN '15-20'
WHEN t.temperature &lt; 25 THEN '20-25'
WHEN t.temperature &lt; 30 THEN '25-30'
ELSE '>=30'
END AS tempBucket
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
) sub
GROUP BY sub.tempBucket
ORDER BY sub.tempBucket
</select>
<!-- D2. 温度直方图1℃分箱 -->
<select id="selectHistogram" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardDistributionVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT FLOOR(t.temperature) AS tempBin, COUNT(*) AS sampleCount
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY FLOOR(t.temperature)
</foreach>
ORDER BY tempBin
</select>
<!-- D3. 温度箱线图原始数据(按测点) -->
<select id="selectBoxplotData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardDistributionVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
t.temperature
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
ORDER BY monitorId, temperature
</select>
<!-- D4. 日历热力图 -->
<select id="selectCalendarHeatmap" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardDistributionVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
FORMAT(CAST(t.collectTime AS DATE), 'yyyy-MM-dd') AS statDate,
ROUND(AVG(t.temperature), 2) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY CAST(t.collectTime AS DATE)
</foreach>
ORDER BY statDate
</select>
<!-- D5. 小时热力图 -->
<select id="selectHourlyHeatmap" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardDistributionVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
FORMAT(CAST(t.collectTime AS DATE), 'yyyy-MM-dd') AS statDate,
DATEPART(HOUR, t.collectTime) AS statHour,
ROUND(AVG(t.temperature), 2) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY CAST(t.collectTime AS DATE), DATEPART(HOUR, t.collectTime)
</foreach>
ORDER BY statDate, statHour
</select>
<!-- ==================== E. 异常预警 ==================== -->
<!-- E1. 高温事件 -->
<select id="selectHighTempEvents" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAnomalyVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
t.temperature,
t.collectTime,
'HIGH_TEMP' AS anomalyType
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
t.temperature >= #{highTempThreshold}
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
ORDER BY collectTime DESC
</select>
<!-- E2. 低温事件 -->
<select id="selectLowTempEvents" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAnomalyVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
t.temperature,
t.collectTime,
'LOW_TEMP' AS anomalyType
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
t.temperature &lt;= #{lowTempThreshold}
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
ORDER BY collectTime DESC
</select>
<!-- E3. 连续高温时段(岛屿分组) -->
<select id="selectContinuousHighTemp" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAnomalyVo">
WITH base AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
flagged AS (
SELECT monitorId, temperature, collectTime, monitorName,
CASE WHEN temperature >= #{highTempThreshold} THEN 1 ELSE 0 END AS isHigh
FROM base
),
grouped AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY monitorId ORDER BY collectTime) -
ROW_NUMBER() OVER (PARTITION BY monitorId, isHigh ORDER BY collectTime) AS grp
FROM flagged
)
SELECT monitorId, monitorName,
MIN(collectTime) AS startTime,
MAX(collectTime) AS endTime,
MAX(temperature) AS maxTemp,
COUNT(*) AS sampleCount
FROM grouped WHERE isHigh = 1
GROUP BY monitorId, monitorName, grp
HAVING COUNT(*) >= 2
ORDER BY monitorId, startTime
</select>
<!-- E4. 温升过快事件 -->
<select id="selectRapidRiseEvents" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAnomalyVo">
WITH base AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.temperature, t.collectTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
seq AS (
SELECT monitorId, collectTime, temperature, monitorName,
LAG(collectTime) OVER (PARTITION BY monitorId ORDER BY collectTime) AS prevTime,
LAG(temperature) OVER (PARTITION BY monitorId ORDER BY collectTime) AS prevTemp
FROM base
)
SELECT monitorId, monitorName, collectTime, prevTime,
prevTemp AS prevTemp,
temperature,
ROUND(
CAST(temperature - prevTemp AS FLOAT) /
NULLIF(CAST(DATEDIFF(SECOND, prevTime, collectTime) AS FLOAT), 0) * 60,
4
) AS risePerMin
FROM seq WHERE prevTime IS NOT NULL
AND CAST(temperature - prevTemp AS FLOAT) /
NULLIF(CAST(DATEDIFF(SECOND, prevTime, collectTime) AS FLOAT), 0) * 60 >= #{rapidRiseThreshold}
ORDER BY risePerMin DESC
</select>
<!-- E5. 温度抖动异常(按小时标准差) -->
<select id="selectJitterAnomalies" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAnomalyVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
FORMAT(t.collectTime, 'yyyy-MM-dd HH:00:00') AS statTime,
ROUND(STDEVP(t.temperature), 4) AS tempStddev,
'JITTER' AS anomalyType
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId),
FORMAT(t.collectTime, 'yyyy-MM-dd HH:00:00')
HAVING STDEVP(t.temperature) >= #{stddevThreshold}
</foreach>
ORDER BY tempStddev DESC
</select>
<!-- ==================== F. 对比分析 ==================== -->
<!-- F1. 测点平均温度排行 -->
<select id="selectAvgTempRanking" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardComparisonVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY avgTemp DESC
</select>
<!-- F2. 测点稳定性排行(标准差升序) -->
<select id="selectStabilityRanking" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardComparisonVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(STDEVP(t.temperature), 4) AS tempStddev
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY tempStddev ASC
</select>
<!-- F3. 今日vs昨日对比拆分为 today / yesterday 两个 CTE避免 GROUP BY 引用 collectTime 列) -->
<select id="selectDailyDiff" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardComparisonVo">
WITH today_avg AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
AVG(t.temperature) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
t.collectTime >= #{todayStartTime} AND t.collectTime &lt; #{todayEndTime}
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
),
yesterday_avg AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
AVG(t.temperature) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
t.collectTime >= #{yesterdayStartTime} AND t.collectTime &lt; #{yesterdayEndTime}
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
)
SELECT t.monitorId, t.monitorName,
ROUND(t.avgTemp, 2) AS todayAvg,
ROUND(y.avgTemp, 2) AS yesterdayAvg,
ROUND(t.avgTemp - y.avgTemp, 2) AS diffValue
FROM today_avg t
LEFT JOIN yesterday_avg y ON t.monitorId = y.monitorId
ORDER BY diffValue DESC
</select>
<!-- F4. 峰值对比 -->
<select id="selectPeakCompare" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardComparisonVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
MAX(t.temperature) AS maxTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY maxTemp DESC
</select>
<!-- F5. 波动幅度对比 -->
<select id="selectFluctuationCompare" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardComparisonVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
MAX(t.temperature) - MIN(t.temperature) AS tempRange
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY tempRange DESC
</select>
<!-- ==================== G. 数据质量 ==================== -->
<!-- G1. 入库延迟分布 -->
<select id="selectDelayDistribution" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardQualityVo">
SELECT sub.delayBucket, COUNT(*) AS sampleCount
FROM (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.collectTime, t.recodeTime,
CASE
WHEN DATEDIFF(SECOND, t.collectTime, t.recodeTime) &lt; 10 THEN '&lt;10s'
WHEN DATEDIFF(SECOND, t.collectTime, t.recodeTime) &lt; 30 THEN '10-30s'
WHEN DATEDIFF(SECOND, t.collectTime, t.recodeTime) &lt; 60 THEN '30-60s'
ELSE '>=60s'
END AS delayBucket
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
) sub
GROUP BY sub.delayBucket
ORDER BY sub.delayBucket
</select>
<!-- G2. 时间逆序可疑数据recodeTime < collectTime -->
<select id="selectTimeReversal" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardQualityVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
t.temperature,
t.collectTime,
t.recodeTime,
DATEDIFF(SECOND, t.collectTime, t.recodeTime) AS delaySeconds
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
t.recodeTime &lt; t.collectTime
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
ORDER BY collectTime DESC
</select>
<!-- G3. 采样间隔异常 -->
<select id="selectSamplingGapAnomalies" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardQualityVo">
WITH base AS (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId, t.collectTime,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
),
seq AS (
SELECT monitorId, collectTime, monitorName,
LAG(collectTime) OVER (PARTITION BY monitorId ORDER BY collectTime) AS prevTime
FROM base
)
SELECT monitorId, monitorName, prevTime, collectTime,
DATEDIFF(SECOND, prevTime, collectTime) AS gapSeconds
FROM seq WHERE prevTime IS NOT NULL
AND DATEDIFF(SECOND, prevTime, collectTime) >= #{gapThreshold}
ORDER BY gapSeconds DESC
</select>
<!-- G4. 数据完整率 -->
<select id="selectCompletenessRate" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardQualityVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
COUNT(*) AS actualCount,
#{expectedCount} AS expectedCount,
ROUND(CAST(COUNT(*) AS FLOAT) / #{expectedCount}, 4) AS completenessRate
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY completenessRate ASC
</select>
<!-- G5. 测点活跃度 -->
<select id="selectMonitorActivity" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardQualityVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
COUNT(*) AS actualCount,
MIN(t.collectTime) AS firstTime,
MAX(t.collectTime) AS lastTime
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY actualCount DESC
</select>
<!-- ==================== H. 高级分析 ==================== -->
<!-- H1. 桑基图数据(温区按时间段流转)
优化:每表内部先做 DISTINCT(monitorId+桶) 缩小数据量,再 UNION ALL 小结果集做全局自关联 -->
<select id="selectSankeyData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
WITH stage AS (
SELECT DISTINCT monitorId, timeBucket, tempBucket FROM (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT DISTINCT t.monitorId,
CASE
WHEN DATEPART(HOUR, t.collectTime) &lt; 6 THEN 'T1'
WHEN DATEPART(HOUR, t.collectTime) &lt; 12 THEN 'T2'
WHEN DATEPART(HOUR, t.collectTime) &lt; 18 THEN 'T3'
ELSE 'T4'
END AS timeBucket,
CASE
WHEN t.temperature &lt; 15 THEN '&lt;15'
WHEN t.temperature &lt; 20 THEN '15-20'
WHEN t.temperature &lt; 25 THEN '20-25'
ELSE '>=25'
END AS tempBucket
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
</foreach>
) raw_stage
),
flow AS (
SELECT
a.timeBucket + '_' + a.tempBucket AS fromNode,
b.timeBucket + '_' + b.tempBucket AS toNode,
COUNT(*) AS flowCount
FROM stage a
INNER JOIN stage b ON a.monitorId = b.monitorId
AND (
(a.timeBucket = 'T1' AND b.timeBucket = 'T2') OR
(a.timeBucket = 'T2' AND b.timeBucket = 'T3') OR
(a.timeBucket = 'T3' AND b.timeBucket = 'T4')
)
GROUP BY a.timeBucket, a.tempBucket, b.timeBucket, b.tempBucket
)
SELECT fromNode, toNode, flowCount FROM flow
ORDER BY fromNode, toNode
</select>
<!-- H2. 主题河流图数据
优化GROUP BY 使用 DATEADD 时间桶(可走索引),外层 CONVERT 格式化输出(仅对聚合后小结果集);
支持动态粒度MINUTE / FIFTEEN_MINUTE / HOUR -->
<select id="selectThemeRiverData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
SELECT
CONVERT(VARCHAR(19), statBucket, 120) AS statTime,
monitorId, monitorName, avgTemp
FROM (
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
<choose>
<when test="granularity == 'HOUR'">
DATEADD(HOUR, DATEDIFF(HOUR, 0, t.collectTime), 0)
</when>
<when test="granularity == 'FIFTEEN_MINUTE'">
DATEADD(MINUTE, (DATEDIFF(MINUTE, 0, t.collectTime) / 15) * 15, 0)
</when>
<otherwise>
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, t.collectTime), 0)
</otherwise>
</choose> AS statBucket,
t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY
<choose>
<when test="granularity == 'HOUR'">
DATEADD(HOUR, DATEDIFF(HOUR, 0, t.collectTime), 0)
</when>
<when test="granularity == 'FIFTEEN_MINUTE'">
DATEADD(MINUTE, (DATEDIFF(MINUTE, 0, t.collectTime) / 15) * 15, 0)
</when>
<otherwise>
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, t.collectTime), 0)
</otherwise>
</choose>,
t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
) sub
ORDER BY statTime, monitorId
</select>
<!-- H3. 矩形树图数据(按测点平均温度+样本数)
每张日表独立聚合UNION ALL 直接输出 -->
<select id="selectTreemapData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp,
COUNT(*) AS sampleCount
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY avgTemp DESC
</select>
<!-- H4. 旭日图数据(温区→测点层级)
每张日表独立聚合UNION ALL 直接输出 -->
<select id="selectSunburstData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT
CASE
WHEN t.temperature &lt; 15 THEN '&lt;15'
WHEN t.temperature &lt; 20 THEN '15-20'
WHEN t.temperature &lt; 25 THEN '20-25'
WHEN t.temperature &lt; 30 THEN '25-30'
ELSE '>=30'
END AS tempBucket,
t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
COUNT(*) AS sampleCount
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY
CASE
WHEN t.temperature &lt; 15 THEN '&lt;15'
WHEN t.temperature &lt; 20 THEN '15-20'
WHEN t.temperature &lt; 25 THEN '20-25'
WHEN t.temperature &lt; 30 THEN '25-30'
ELSE '>=30'
END,
t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY tempBucket, monitorId
</select>
<!-- H5. 平行坐标图数据(多维温度画像)
每张日表独立聚合UNION ALL 直接输出 -->
<select id="selectParallelData" resultType="org.dromara.ems.report.domain.vo.tempboard.TempBoardAdvancedVo">
<foreach collection="tableNames" item="tableName" separator=" UNION ALL ">
SELECT t.monitorId,
COALESCE(ebmi.monitor_name, t.monitorId) AS monitorName,
ROUND(AVG(t.temperature), 2) AS avgTemp,
ROUND(MAX(t.temperature), 2) AS maxTemp,
ROUND(MIN(t.temperature), 2) AS minTemp,
ROUND(STDEVP(t.temperature), 4) AS tempStddev,
ROUND(AVG(CAST(DATEDIFF(SECOND, t.collectTime, t.recodeTime) AS FLOAT)), 2) AS avgDelay
FROM ${tableName} t
<include refid="baseJoin"/>
<where>
<include refid="timeFilter"/>
<include refid="tempFilter"/>
</where>
GROUP BY t.monitorId, COALESCE(ebmi.monitor_name, t.monitorId)
</foreach>
ORDER BY avgTemp DESC
</select>
</mapper>