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

<?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>