feat: 优化注塑OEE利用率计算与查询性能

引入利用率类型单选查询(日/周/总),将OEE指标计算下沉至数据库并增加批量查询,消除N+1扫描问题。
master
zch 4 weeks ago
parent 570f6d91ed
commit 37241b5017

@ -32,10 +32,11 @@ public class InjectionOeeController extends BaseController {
@RequestParam(required = false) String deviceCode,
@RequestParam(required = false) String beginTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) String shiftType) {
@RequestParam(required = false) String shiftType,
@RequestParam(required = false) String availabilityType) {
List<InjectionOeeAnalysisVo> list = injectionOeeService.getInjectionOeeAnalysis(
deviceCode, beginTime, endTime, shiftType);
deviceCode, beginTime, endTime, shiftType, availabilityType);
return success(list);
}

@ -121,15 +121,15 @@ public class InjectionOeeAnalysisVo extends BaseEntity implements Serializable {
@JsonProperty("SHIFT_NAME")
private String SHIFT_NAME;
/** 日利用率 */
/** 日利用率:按页面传入统计日期计算,不固定取服务器当天 */
@JsonProperty("TODAY_AVAILABILITY")
private Double TODAY_AVAILABILITY;
/** 周利用率 */
/** 周利用率:按页面结束日所在自然周计算 */
@JsonProperty("WEEK_AVAILABILITY")
private Double WEEK_AVAILABILITY;
/** 总利用率 */
/** 总利用率:按页面选择日期范围计算 */
@JsonProperty("TOTAL_AVAILABILITY")
private Double TOTAL_AVAILABILITY;

@ -1,6 +1,7 @@
package com.aucma.report.mapper;
import com.aucma.report.domain.vo.InjectionOeeAnalysisVo;
import com.aucma.report.domain.vo.ParamRawPoint;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -57,6 +58,20 @@ public interface InjectionOeeMapper {
@Param("endTime") Date endTime,
@Param("tableSuffixes") List<String> tableSuffixes);
/**
*
*
* @param deviceCodes
* @param endTime
* @param tableSuffixes
* @param oldDevice OLD
* @return DEVICE_CODECAVITIES Map
*/
List<Map<String, Object>> selectLatestCavitiesBatch(@Param("deviceCodes") List<String> deviceCodes,
@Param("endTime") Date endTime,
@Param("tableSuffixes") List<String> tableSuffixes,
@Param("oldDevice") boolean oldDevice);
/**
* 0
*
@ -71,6 +86,38 @@ public interface InjectionOeeMapper {
@Param("endTime") Date endTime,
@Param("tableSuffixes") List<String> tableSuffixes);
/**
* Java
*
* @param deviceCode
* @param beginTime
* @param endTime
* @param tableSuffixes
* @param oldDevice OLD
* @return SAMPLE_COUNTAVG_SECONDSSTDDEV_SECONDSMEDIAN_SECONDS Map
*/
Map<String, Object> selectCycleTimeStats(@Param("deviceCode") String deviceCode,
@Param("beginTime") Date beginTime,
@Param("endTime") Date endTime,
@Param("tableSuffixes") List<String> tableSuffixes,
@Param("oldDevice") boolean oldDevice);
/**
*
*
* @param deviceCodes
* @param beginTime
* @param endTime
* @param tableSuffixes
* @param oldDevice OLD
* @return Map
*/
List<Map<String, Object>> selectCycleTimeStatsBatch(@Param("deviceCodes") List<String> deviceCodes,
@Param("beginTime") Date beginTime,
@Param("endTime") Date endTime,
@Param("tableSuffixes") List<String> tableSuffixes,
@Param("oldDevice") boolean oldDevice);
/**
*
*
@ -94,4 +141,42 @@ public interface InjectionOeeMapper {
@Param("paramName") String paramName,
@Param("beginDate") Date beginDate,
@Param("endDate") Date endDate);
/**
* COLLECT_TIME
* Java OEE N+1
*
* @param deviceCode
* @param paramName
* @param minQueryBegin 线
* @param maxQueryEnd
* @param tableSuffixes
* @param oldDevice OLD
* @return
*/
List<ParamRawPoint> selectRawParamValues(@Param("deviceCode") String deviceCode,
@Param("paramName") String paramName,
@Param("minQueryBegin") Date minQueryBegin,
@Param("maxQueryEnd") Date maxQueryEnd,
@Param("tableSuffixes") List<String> tableSuffixes,
@Param("oldDevice") boolean oldDevice);
/**
*
* OEE
*
* @param deviceCodes
* @param paramName
* @param minQueryBegin 线
* @param maxQueryEnd
* @param tableSuffixes
* @param oldDevice OLD
* @return
*/
List<ParamRawPoint> selectRawParamValuesBatch(@Param("deviceCodes") List<String> deviceCodes,
@Param("paramName") String paramName,
@Param("minQueryBegin") Date minQueryBegin,
@Param("maxQueryEnd") Date maxQueryEnd,
@Param("tableSuffixes") List<String> tableSuffixes,
@Param("oldDevice") boolean oldDevice);
}

@ -18,7 +18,12 @@ public interface IInjectionOeeService {
* @param beginTimeStr (: YYYY-MM-DD)
* @param endTimeStr (: YYYY-MM-DD)
* @param shiftType (ALL-, DAY-, NIGHT-)
* @param availabilityType (DAY-, WEEK-, TOTAL-)
* @return OEE
*/
List<InjectionOeeAnalysisVo> getInjectionOeeAnalysis(String deviceCode, String beginTimeStr, String endTimeStr, String shiftType);
List<InjectionOeeAnalysisVo> getInjectionOeeAnalysis(String deviceCode,
String beginTimeStr,
String endTimeStr,
String shiftType,
String availabilityType);
}

@ -2,6 +2,7 @@ package com.aucma.report.service.impl;
import com.aucma.base.support.DeviceParamTableRouter;
import com.aucma.report.domain.vo.InjectionOeeAnalysisVo;
import com.aucma.report.domain.vo.ParamRawPoint;
import com.aucma.report.mapper.InjectionOeeMapper;
import com.aucma.report.service.IInjectionOeeService;
import org.slf4j.Logger;
@ -20,6 +21,7 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -27,7 +29,7 @@ import java.util.Map;
* OEE
*
* <p>OEE = A() * P() * Q()A OEE OEE
* OEE 便</p>
* OEE 便</p>
*
* @author Antigravity
*/
@ -49,6 +51,10 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
private static final String SHIFT_DAY = "DAY";
private static final String SHIFT_NIGHT = "NIGHT";
private static final String AVAILABILITY_DAY = "DAY";
private static final String AVAILABILITY_WEEK = "WEEK";
private static final String AVAILABILITY_TOTAL = "TOTAL";
private static final String OLD_DEVICE_CODE_PREFIX = "OLD-";
private static final double OLD_DEVICE_ESTIMATE_MIN_RATE = 0.70D;
private static final double OLD_DEVICE_ESTIMATE_MAX_RATE = 0.90D;
@ -64,8 +70,8 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
/** OLD 手工设备采集稀疏,基线窗口放宽到 24 小时。 */
private static final int OLD_BASELINE_HOURS = -24;
/** 周利用率固定看最近 7 个生产日。 */
private static final int WEEK_DAYS = 6;
/** 周期时间有效样本下限,样本过少时用 1.0 兜底,避免少量异常点放大 P。 */
private static final int MIN_CYCLE_SAMPLE_COUNT = 10;
@Autowired
private InjectionOeeMapper injectionOeeMapper;
@ -74,7 +80,11 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
private DeviceParamTableRouter deviceParamTableRouter;
@Override
public List<InjectionOeeAnalysisVo> getInjectionOeeAnalysis(String deviceCode, String beginTimeStr, String endTimeStr, String shiftType) {
public List<InjectionOeeAnalysisVo> getInjectionOeeAnalysis(String deviceCode,
String beginTimeStr,
String endTimeStr,
String shiftType,
String availabilityType) {
List<InjectionOeeAnalysisVo> targetDevices = injectionOeeMapper.selectTargetDevices(deviceCode);
if (CollectionUtils.isEmpty(targetDevices)) {
return Collections.emptyList();
@ -84,47 +94,49 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
sdf.setLenient(false);
String normalizedShiftType = normalizeShiftType(shiftType);
String normalizedAvailabilityType = normalizeAvailabilityType(availabilityType);
Date todayDate = truncateToDay(new Date());
Date weekBeginDate = addDays(todayDate, -WEEK_DAYS);
DateRange totalRange = resolveTotalRange(beginTimeStr, endTimeStr, sdf, todayDate);
Date defaultReportDate = addDays(todayDate, -1);
DateRange selectedRange = resolveSelectedRange(beginTimeStr, endTimeStr, sdf, defaultReportDate, todayDate);
DateRange availabilityRange = resolveAvailabilityRange(selectedRange, normalizedAvailabilityType, todayDate);
List<MetricWindow> totalWindows = buildShiftWindows(totalRange.beginDate, totalRange.endDate, normalizedShiftType);
List<MetricWindow> weekWindows = buildShiftWindows(weekBeginDate, todayDate, normalizedShiftType);
List<MetricWindow> todayWindows = buildShiftWindows(todayDate, todayDate, normalizedShiftType);
List<MetricWindow> metricWindows = buildShiftWindows(availabilityRange.beginDate,
availabilityRange.endDate, normalizedShiftType);
Date queryBeginTime = firstWindowBegin(metricWindows);
Date queryEndTime = lastWindowEnd(metricWindows);
QualityResult quality = calculateQuality(queryBeginTime, queryEndTime);
MetricCache metricCache = loadMetricCache(targetDevices, metricWindows);
List<InjectionOeeAnalysisVo> results = new ArrayList<>();
for (InjectionOeeAnalysisVo device : targetDevices) {
InjectionOeeAnalysisVo vo = buildOeeRow(device, normalizedShiftType, totalWindows, weekWindows, todayWindows);
InjectionOeeAnalysisVo vo = buildOeeRow(device, normalizedShiftType, normalizedAvailabilityType,
metricWindows, quality, metricCache);
results.add(vo);
}
fillMissingOldDeviceOeeByReference(results);
fillMissingOldDeviceOeeByReference(results, normalizedAvailabilityType);
results.sort(Comparator.comparing(InjectionOeeAnalysisVo::getDEVICE_CODE));
results.sort(this::compareByDeviceDisplayOrder);
return results;
}
private InjectionOeeAnalysisVo buildOeeRow(InjectionOeeAnalysisVo device,
String shiftType,
List<MetricWindow> totalWindows,
List<MetricWindow> weekWindows,
List<MetricWindow> todayWindows) {
String availabilityType,
List<MetricWindow> metricWindows,
QualityResult quality,
MetricCache metricCache) {
String deviceCode = device.getDEVICE_CODE();
CounterSummary totalRun = selectCounterSummary(deviceCode, PARAM_RUN_SECONDS, totalWindows);
CounterSummary totalShots = selectCounterSummary(deviceCode, PARAM_SHOTS, totalWindows);
CounterSummary weekRun = selectCounterSummary(deviceCode, PARAM_RUN_SECONDS, weekWindows);
CounterSummary todayRun = selectCounterSummary(deviceCode, PARAM_RUN_SECONDS, todayWindows);
List<ParamRawPoint> runRawPoints = metricCache.getRunRawPoints(deviceCode);
List<ParamRawPoint> shotsRawPoints = metricCache.getShotRawPoints(deviceCode);
Date queryBeginTime = firstWindowBegin(totalWindows);
Date queryEndTime = lastWindowEnd(totalWindows);
List<String> tableSuffixes = resolveTableSuffixes(queryBeginTime, queryEndTime);
CounterSummary totalRun = calculateCounterSummary(deviceCode, metricWindows, runRawPoints);
CounterSummary totalShots = calculateCounterSummary(deviceCode, metricWindows, shotsRawPoints);
BigDecimal cavities = resolveCavities(deviceCode, queryEndTime, tableSuffixes);
BigDecimal cavities = resolveCavities(deviceCode, metricCache);
boolean cavitiesEstimated = cavities.compareTo(BigDecimal.ONE) == 0;
CycleResult cycle = calculateStandardCycle(injectionOeeMapper.selectCycleTimeSamples(
deviceCode, queryBeginTime, queryEndTime, tableSuffixes));
QualityResult quality = calculateQuality(queryBeginTime, queryEndTime);
CycleResult cycle = calculateStandardCycle(metricCache.getCycleStats(deviceCode));
BigDecimal plannedSeconds = BigDecimal.valueOf(totalRun.planSeconds);
BigDecimal runSeconds = BigDecimal.valueOf(totalRun.value);
@ -149,9 +161,7 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
vo.setQUALITY(toDouble(qualityRate));
vo.setOEE(toDouble(oee));
vo.setTOTAL_AVAILABILITY(toDouble(capRate(availability)));
vo.setWEEK_AVAILABILITY(toDouble(capRate(calcRate(BigDecimal.valueOf(weekRun.value), BigDecimal.valueOf(weekRun.planSeconds)))));
vo.setTODAY_AVAILABILITY(toDouble(capRate(calcRate(BigDecimal.valueOf(todayRun.value), BigDecimal.valueOf(todayRun.planSeconds)))));
applyAvailabilityTypeValue(vo, availabilityType, capRate(availability));
vo.setPLANNED_TIME_MINUTES(totalRun.planSeconds / 60);
vo.setDOWNTIME_MINUTES(Math.max(0L, (totalRun.planSeconds - totalRun.value) / 60));
@ -175,41 +185,308 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return vo;
}
private CounterSummary selectCounterSummary(String deviceCode, String paramName, List<MetricWindow> windows) {
/**
* OEE SQL
*/
private MetricCache loadMetricCache(List<InjectionOeeAnalysisVo> devices,
List<MetricWindow> metricWindows) {
MetricCache metricCache = new MetricCache();
loadRawPointCache(metricCache.runRawPointsByDevice, devices, PARAM_RUN_SECONDS, metricWindows);
loadRawPointCache(metricCache.shotRawPointsByDevice, devices, PARAM_SHOTS, metricWindows);
Date queryBeginTime = firstWindowBegin(metricWindows);
Date queryEndTime = lastWindowEnd(metricWindows);
List<String> tableSuffixes = resolveTableSuffixes(queryBeginTime, queryEndTime);
loadCavityCache(metricCache.cavitiesByDevice, devices, queryEndTime, tableSuffixes);
loadCycleStatsCache(metricCache.cycleStatsByDevice, devices, queryBeginTime, queryEndTime, tableSuffixes);
return metricCache;
}
private void loadRawPointCache(Map<String, List<ParamRawPoint>> targetMap,
List<InjectionOeeAnalysisVo> devices,
String paramName,
List<MetricWindow> windows) {
loadRawPointCache(targetMap, extractDeviceCodes(devices, false), paramName, windows,
AUTO_BASELINE_HOURS, false);
loadRawPointCache(targetMap, extractDeviceCodes(devices, true), paramName, windows,
OLD_BASELINE_HOURS, true);
for (List<ParamRawPoint> rawPoints : targetMap.values()) {
rawPoints.sort(Comparator.comparing(ParamRawPoint::getCollectTime));
}
}
private void loadRawPointCache(Map<String, List<ParamRawPoint>> targetMap,
List<String> deviceCodes,
String paramName,
List<MetricWindow> windows,
int baselineHours,
boolean oldDevice) {
if (CollectionUtils.isEmpty(deviceCodes) || CollectionUtils.isEmpty(windows)) {
return;
}
List<QueryRange> queryRanges = buildMergedQueryRanges(windows, baselineHours);
for (QueryRange queryRange : queryRanges) {
List<String> tableSuffixes = resolveTableSuffixes(queryRange.beginTime, queryRange.endTime);
if (!oldDevice && CollectionUtils.isEmpty(tableSuffixes)) {
continue;
}
List<ParamRawPoint> rawPoints = injectionOeeMapper.selectRawParamValuesBatch(
deviceCodes,
paramName,
queryRange.beginTime,
queryRange.endTime,
tableSuffixes,
oldDevice
);
mergeRawPointCache(targetMap, rawPoints);
}
}
private void mergeRawPointCache(Map<String, List<ParamRawPoint>> targetMap, List<ParamRawPoint> rawPoints) {
if (CollectionUtils.isEmpty(rawPoints)) {
return;
}
for (ParamRawPoint rawPoint : rawPoints) {
if (rawPoint == null || !StringUtils.hasText(rawPoint.getDeviceCode())) {
continue;
}
List<ParamRawPoint> deviceRawPoints = targetMap.get(rawPoint.getDeviceCode());
if (deviceRawPoints == null) {
deviceRawPoints = new ArrayList<>();
targetMap.put(rawPoint.getDeviceCode(), deviceRawPoints);
}
deviceRawPoints.add(rawPoint);
}
}
private void loadCavityCache(Map<String, BigDecimal> targetMap,
List<InjectionOeeAnalysisVo> devices,
Date endTime,
List<String> tableSuffixes) {
loadCavityCache(targetMap, extractDeviceCodes(devices, false), endTime, tableSuffixes, false);
loadCavityCache(targetMap, extractDeviceCodes(devices, true), endTime, tableSuffixes, true);
}
private void loadCavityCache(Map<String, BigDecimal> targetMap,
List<String> deviceCodes,
Date endTime,
List<String> tableSuffixes,
boolean oldDevice) {
if (CollectionUtils.isEmpty(deviceCodes)) {
return;
}
if (!oldDevice && CollectionUtils.isEmpty(tableSuffixes)) {
return;
}
List<Map<String, Object>> rows = injectionOeeMapper.selectLatestCavitiesBatch(
deviceCodes, endTime, tableSuffixes, oldDevice);
if (CollectionUtils.isEmpty(rows)) {
return;
}
for (Map<String, Object> row : rows) {
String deviceCode = toMapString(row, "DEVICE_CODE");
if (!StringUtils.hasText(deviceCode)) {
continue;
}
BigDecimal cavities = toBigDecimal(getMapValue(row, "CAVITIES"));
if (cavities.compareTo(BigDecimal.ZERO) > 0) {
targetMap.put(deviceCode, cavities);
}
}
}
private void loadCycleStatsCache(Map<String, Map<String, Object>> targetMap,
List<InjectionOeeAnalysisVo> devices,
Date beginTime,
Date endTime,
List<String> tableSuffixes) {
loadCycleStatsCache(targetMap, extractDeviceCodes(devices, false), beginTime, endTime,
tableSuffixes, false);
loadCycleStatsCache(targetMap, extractDeviceCodes(devices, true), beginTime, endTime,
tableSuffixes, true);
}
private void loadCycleStatsCache(Map<String, Map<String, Object>> targetMap,
List<String> deviceCodes,
Date beginTime,
Date endTime,
List<String> tableSuffixes,
boolean oldDevice) {
if (CollectionUtils.isEmpty(deviceCodes)) {
return;
}
if (!oldDevice && CollectionUtils.isEmpty(tableSuffixes)) {
return;
}
List<Map<String, Object>> rows = injectionOeeMapper.selectCycleTimeStatsBatch(
deviceCodes, beginTime, endTime, tableSuffixes, oldDevice);
if (CollectionUtils.isEmpty(rows)) {
return;
}
for (Map<String, Object> row : rows) {
String deviceCode = toMapString(row, "DEVICE_CODE");
if (StringUtils.hasText(deviceCode)) {
targetMap.put(deviceCode, row);
}
}
}
private List<String> extractDeviceCodes(List<InjectionOeeAnalysisVo> devices, boolean oldDevice) {
List<String> deviceCodes = new ArrayList<>();
for (InjectionOeeAnalysisVo device : devices) {
if (device == null || !StringUtils.hasText(device.getDEVICE_CODE())) {
continue;
}
if (isOldDeviceCode(device.getDEVICE_CODE()) == oldDevice) {
deviceCodes.add(device.getDEVICE_CODE());
}
}
return deviceCodes;
}
private Object getMapValue(Map<String, Object> map, String key) {
if (map == null || key == null) {
return null;
}
if (map.containsKey(key)) {
return map.get(key);
}
return map.get(key.toLowerCase());
}
private String toMapString(Map<String, Object> map, String key) {
Object value = getMapValue(map, key);
return value == null ? null : value.toString();
}
/**
* baseline reset
* SQL selectCounterDeltaByWindow
*/
private long calculateCounterDeltaInMemory(String deviceCode, MetricWindow window, List<ParamRawPoint> rawPoints) {
if (CollectionUtils.isEmpty(rawPoints)) {
return 0L;
}
boolean isOld = isOldDeviceCode(deviceCode);
int baselineHours = isOld ? OLD_BASELINE_HOURS : AUTO_BASELINE_HOURS;
Date baselineBeginTime = addHours(window.beginTime, baselineHours);
BigDecimal deltaSum = BigDecimal.ZERO;
BigDecimal prevValue = null;
for (ParamRawPoint point : rawPoints) {
Date collectTime = point.getCollectTime();
if (collectTime.before(baselineBeginTime)) {
continue;
}
if (!collectTime.before(window.endTime)) {
// rawPoints 已按采集时间升序排列,越过窗口后无需继续扫描。
break;
}
BigDecimal currValue = point.getParamValue();
if (collectTime.before(window.beginTime)) {
// 仅作为对比的基线,不累加增量本身
prevValue = currValue;
} else {
if (prevValue == null) {
// 没有 baseline且这是窗口内第一个点设为参考点不计算增量对应 SQL 中的 NULL
prevValue = currValue;
} else {
if (currValue.compareTo(prevValue) > 0) {
deltaSum = deltaSum.add(currValue.subtract(prevValue));
} else if (currValue.compareTo(prevValue) < 0) {
// 计数器在两次采集中间发生了重置,直接加当前值
deltaSum = deltaSum.add(currValue);
}
prevValue = currValue;
}
}
}
return safeLong(deltaSum);
}
/**
*
*/
private CounterSummary calculateCounterSummary(String deviceCode, List<MetricWindow> windows, List<ParamRawPoint> rawPoints) {
long value = 0L;
long planSeconds = 0L;
for (MetricWindow window : windows) {
value += selectCounterDelta(deviceCode, paramName, window);
value += calculateCounterDeltaInMemory(deviceCode, window, rawPoints);
planSeconds += window.planSeconds;
}
return new CounterSummary(value, planSeconds);
}
/**
* delta-sum
*
* <p>/ 07:0019:00 </p>
* /
*/
private long selectCounterDelta(String deviceCode, String paramName, MetricWindow window) {
Date autoBaselineBeginTime = addHours(window.beginTime, AUTO_BASELINE_HOURS);
Date oldBaselineBeginTime = addHours(window.beginTime, OLD_BASELINE_HOURS);
List<String> tableSuffixes = resolveTableSuffixes(autoBaselineBeginTime, window.endTime);
BigDecimal delta = injectionOeeMapper.selectCounterDeltaByWindow(
deviceCode,
paramName,
window.beginTime,
window.endTime,
autoBaselineBeginTime,
oldBaselineBeginTime,
tableSuffixes
);
return safeLong(delta);
private List<ParamRawPoint> selectRawPoints(String deviceCode, String paramName, List<MetricWindow> windows) {
if (CollectionUtils.isEmpty(windows)) {
return Collections.emptyList();
}
boolean oldDevice = isOldDeviceCode(deviceCode);
int baselineHours = oldDevice ? OLD_BASELINE_HOURS : AUTO_BASELINE_HOURS;
List<QueryRange> queryRanges = buildMergedQueryRanges(windows, baselineHours);
if (CollectionUtils.isEmpty(queryRanges)) {
return Collections.emptyList();
}
List<ParamRawPoint> rawPoints = new ArrayList<>();
for (QueryRange queryRange : queryRanges) {
List<String> tableSuffixes = resolveTableSuffixes(queryRange.beginTime, queryRange.endTime);
rawPoints.addAll(injectionOeeMapper.selectRawParamValues(
deviceCode,
paramName,
queryRange.beginTime,
queryRange.endTime,
tableSuffixes,
oldDevice
));
}
rawPoints.sort(Comparator.comparing(ParamRawPoint::getCollectTime));
return rawPoints;
}
/**
* 线/穿
*/
private List<QueryRange> buildMergedQueryRanges(List<MetricWindow> windows, int baselineHours) {
List<QueryRange> ranges = new ArrayList<>();
for (MetricWindow window : windows) {
ranges.add(new QueryRange(addHours(window.beginTime, baselineHours), window.endTime));
}
if (ranges.isEmpty()) {
return Collections.emptyList();
}
ranges.sort(Comparator.comparing(QueryRange::getBeginTime));
List<QueryRange> mergedRanges = new ArrayList<>();
for (QueryRange range : ranges) {
if (mergedRanges.isEmpty()) {
mergedRanges.add(range);
continue;
}
QueryRange lastRange = mergedRanges.get(mergedRanges.size() - 1);
if (range.beginTime.after(lastRange.endTime)) {
mergedRanges.add(range);
} else if (range.endTime.after(lastRange.endTime)) {
lastRange.endTime = range.endTime;
}
}
return mergedRanges;
}
/**
* 使 OEE OLD
*/
private void fillMissingOldDeviceOeeByReference(List<InjectionOeeAnalysisVo> list) {
private void fillMissingOldDeviceOeeByReference(List<InjectionOeeAnalysisVo> list, String availabilityType) {
if (CollectionUtils.isEmpty(list)) {
return;
}
@ -219,8 +496,6 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return;
}
List<BigDecimal> weekAvailabilityReferences = buildReferenceRates(list, InjectionOeeAnalysisVo::getWEEK_AVAILABILITY);
List<BigDecimal> todayAvailabilityReferences = buildReferenceRates(list, InjectionOeeAnalysisVo::getTODAY_AVAILABILITY);
List<BigDecimal> performanceReferences = buildReferenceRates(list, InjectionOeeAnalysisVo::getPERFORMANCE);
List<Long> shotReferences = buildReferenceLongValues(list, InjectionOeeAnalysisVo::getSHOTS);
List<Long> usedEstimatedShots = new ArrayList<>();
@ -233,12 +508,10 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
BigDecimal estimateRate = buildOldDeviceEstimateFactor(item, missingOldDeviceIndex);
BigDecimal availability = buildOldDeviceEstimateRate(availabilityReferences, estimateRate);
BigDecimal weekAvailability = buildOldDeviceEstimateRate(weekAvailabilityReferences, estimateRate);
BigDecimal todayAvailability = buildOldDeviceEstimateRate(todayAvailabilityReferences, estimateRate);
BigDecimal performance = buildOldDeviceEstimateRate(performanceReferences, estimateRate);
Long shots = buildOldDeviceEstimateLong(shotReferences, estimateRate, usedEstimatedShots, missingOldDeviceIndex);
applyOldDeviceEstimate(item, availability, weekAvailability, todayAvailability, performance, shots);
applyOldDeviceEstimate(item, availabilityType, availability, performance, shots);
usedEstimatedShots.add(shots);
missingOldDeviceIndex++;
}
@ -257,8 +530,69 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
private boolean isOldDevice(InjectionOeeAnalysisVo item) {
return item != null
&& item.getDEVICE_CODE() != null
&& item.getDEVICE_CODE().trim().startsWith(OLD_DEVICE_CODE_PREFIX);
&& isOldDeviceCode(item.getDEVICE_CODE());
}
private boolean isOldDeviceCode(String deviceCode) {
return deviceCode != null
&& deviceCode.trim().startsWith(OLD_DEVICE_CODE_PREFIX);
}
private void applyAvailabilityTypeValue(InjectionOeeAnalysisVo vo, String availabilityType, BigDecimal availability) {
if (AVAILABILITY_WEEK.equals(availabilityType)) {
vo.setWEEK_AVAILABILITY(toDouble(availability));
return;
}
if (AVAILABILITY_TOTAL.equals(availabilityType)) {
vo.setTOTAL_AVAILABILITY(toDouble(availability));
return;
}
vo.setTODAY_AVAILABILITY(toDouble(availability));
}
private int compareByDeviceDisplayOrder(InjectionOeeAnalysisVo left, InjectionOeeAnalysisVo right) {
String leftCode = left == null ? null : left.getDEVICE_CODE();
String rightCode = right == null ? null : right.getDEVICE_CODE();
int priorityCompare = Integer.compare(resolveDeviceDisplayPriority(leftCode), resolveDeviceDisplayPriority(rightCode));
if (priorityCompare != 0) {
return priorityCompare;
}
int numberCompare = Integer.compare(resolveDeviceDisplayNumber(leftCode), resolveDeviceDisplayNumber(rightCode));
if (numberCompare != 0) {
return numberCompare;
}
return String.valueOf(leftCode).compareTo(String.valueOf(rightCode));
}
private int resolveDeviceDisplayPriority(String deviceCode) {
if (deviceCode == null) {
return 1;
}
String normalizedCode = deviceCode.trim();
if (normalizedCode.startsWith("YZM-")) {
return 0;
}
if (normalizedCode.startsWith(OLD_DEVICE_CODE_PREFIX)) {
return 2;
}
return 1;
}
private int resolveDeviceDisplayNumber(String deviceCode) {
if (deviceCode == null) {
return Integer.MAX_VALUE;
}
String normalizedCode = deviceCode.trim();
int dashIndex = normalizedCode.lastIndexOf('-');
if (dashIndex < 0 || dashIndex == normalizedCode.length() - 1) {
return Integer.MAX_VALUE;
}
try {
// 为什么按编号排序:现场设备编码 YZM-02 / OLD-02 比字典序更符合车间页面阅读习惯。
return Integer.parseInt(normalizedCode.substring(dashIndex + 1));
} catch (NumberFormatException e) {
return Integer.MAX_VALUE;
}
}
private List<BigDecimal> buildReferenceRates(List<InjectionOeeAnalysisVo> list, RateAccessor accessor) {
@ -325,9 +659,8 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
}
private void applyOldDeviceEstimate(InjectionOeeAnalysisVo item,
String availabilityType,
BigDecimal availability,
BigDecimal weekAvailability,
BigDecimal todayAvailability,
BigDecimal performance,
Long shots) {
BigDecimal quality = BigDecimal.valueOf(item.getQUALITY() == null ? 1D : item.getQUALITY().doubleValue());
@ -338,9 +671,7 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
BigDecimal cavities = BigDecimal.valueOf(item.getCAVITIES() == null ? 1L : item.getCAVITIES().longValue());
item.setAVAILABILITY(toDouble(availability));
item.setTOTAL_AVAILABILITY(toDouble(availability));
item.setWEEK_AVAILABILITY(toDouble(weekAvailability));
item.setTODAY_AVAILABILITY(toDouble(todayAvailability));
applyAvailabilityTypeValue(item, availabilityType, availability);
item.setPERFORMANCE(toDouble(performance));
item.setDIAGNOSTIC_PERFORMANCE(toDouble(performance));
item.setOEE(toDouble(oee));
@ -482,8 +813,8 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return new MetricWindow(begin.getTime(), end.getTime(), SHIFT_PLAN_SECONDS, shiftType);
}
private BigDecimal resolveCavities(String deviceCode, Date endTime, List<String> tableSuffixes) {
BigDecimal cavities = injectionOeeMapper.selectLatestCavities(deviceCode, endTime, tableSuffixes);
private BigDecimal resolveCavities(String deviceCode, MetricCache metricCache) {
BigDecimal cavities = metricCache.cavitiesByDevice.get(deviceCode);
if (cavities == null || cavities.compareTo(BigDecimal.ZERO) <= 0) {
// 为什么默认 1模腔数缺失时仍可计算开模数对应件数但必须通过降级说明暴露给业务。
return BigDecimal.ONE;
@ -491,34 +822,24 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return cavities;
}
private CycleResult calculateStandardCycle(List<BigDecimal> cycleSamples) {
private CycleResult calculateStandardCycle(Map<String, Object> cycleStats) {
CycleResult result = new CycleResult();
List<BigDecimal> validSamples = new ArrayList<>();
if (cycleSamples != null) {
for (BigDecimal sample : cycleSamples) {
if (sample != null && sample.compareTo(BigDecimal.ZERO) > 0) {
validSamples.add(sample);
}
}
}
if (validSamples.size() < 20) {
if (cycleStats == null || cycleStats.isEmpty()) {
result.cycleSeconds = BigDecimal.ZERO;
result.estimated = true;
result.source = "标准周期兜底";
return result;
}
List<BigDecimal> secondSamples = new ArrayList<>();
BigDecimal sum = BigDecimal.ZERO;
BigDecimal microsecondDivisor = new BigDecimal("1000000");
for (BigDecimal sample : validSamples) {
BigDecimal second = sample.divide(microsecondDivisor, 6, RoundingMode.HALF_UP);
secondSamples.add(second);
sum = sum.add(second);
int sampleCount = toBigDecimal(cycleStats.get("SAMPLE_COUNT")).intValue();
if (sampleCount < MIN_CYCLE_SAMPLE_COUNT) {
result.cycleSeconds = BigDecimal.ZERO;
result.estimated = true;
result.source = "标准周期兜底";
return result;
}
BigDecimal mean = sum.divide(BigDecimal.valueOf(secondSamples.size()), 6, RoundingMode.HALF_UP);
BigDecimal mean = toBigDecimal(cycleStats.get("AVG_SECONDS"));
if (mean.compareTo(BigDecimal.ZERO) <= 0) {
result.cycleSeconds = BigDecimal.ZERO;
result.estimated = true;
@ -526,32 +847,17 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return result;
}
BigDecimal varianceSum = BigDecimal.ZERO;
for (BigDecimal second : secondSamples) {
BigDecimal diff = second.subtract(mean);
varianceSum = varianceSum.add(diff.multiply(diff));
}
BigDecimal variance = varianceSum.divide(BigDecimal.valueOf(secondSamples.size() - 1), 6, RoundingMode.HALF_UP);
double cv = Math.sqrt(variance.doubleValue()) / mean.doubleValue();
if (cv >= 0.3) {
BigDecimal median = toBigDecimal(cycleStats.get("MEDIAN_SECONDS"));
if (median.compareTo(BigDecimal.ZERO) <= 0) {
result.cycleSeconds = BigDecimal.ZERO;
result.estimated = true;
result.source = "标准周期兜底";
return result;
}
secondSamples.sort(Comparator.naturalOrder());
int count = secondSamples.size();
BigDecimal median;
if (count % 2 == 1) {
median = secondSamples.get(count / 2);
} else {
median = secondSamples.get(count / 2 - 1).add(secondSamples.get(count / 2))
.divide(new BigDecimal("2"), 6, RoundingMode.HALF_UP);
}
result.cycleSeconds = median;
result.cycleSeconds = median.setScale(6, RoundingMode.HALF_UP);
result.estimated = false;
// 为什么不用离散系数直接否决:注塑换模、空循环会拉高波动,中位数已经承担抗极端值职责。
result.source = "设备采集周期中位数";
return result;
}
@ -649,6 +955,16 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return SHIFT_ALL;
}
private String normalizeAvailabilityType(String availabilityType) {
if (AVAILABILITY_WEEK.equalsIgnoreCase(availabilityType)) {
return AVAILABILITY_WEEK;
}
if (AVAILABILITY_TOTAL.equalsIgnoreCase(availabilityType)) {
return AVAILABILITY_TOTAL;
}
return AVAILABILITY_DAY;
}
private String resolveShiftName(String shiftType) {
if (SHIFT_DAY.equals(shiftType)) {
return "白班";
@ -659,23 +975,60 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return "全部班次";
}
private DateRange resolveTotalRange(String beginTimeStr, String endTimeStr, SimpleDateFormat sdf, Date todayDate) {
Date beginDate = parseDay(beginTimeStr, sdf, todayDate);
Date endDate = parseDay(endTimeStr, sdf, todayDate);
private DateRange resolveSelectedRange(String beginTimeStr,
String endTimeStr,
SimpleDateFormat sdf,
Date defaultDate,
Date maxDate) {
Date beginDate = parseDay(beginTimeStr, sdf, defaultDate);
Date endDate = parseDay(endTimeStr, sdf, defaultDate);
if (beginDate.after(endDate)) {
Date tmp = beginDate;
beginDate = endDate;
endDate = tmp;
}
if (beginDate.after(todayDate)) {
return new DateRange(todayDate, todayDate);
if (beginDate.after(maxDate)) {
return new DateRange(maxDate, maxDate);
}
if (endDate.after(todayDate)) {
endDate = todayDate;
if (endDate.after(maxDate)) {
endDate = maxDate;
}
return new DateRange(beginDate, endDate);
}
private DateRange resolveAvailabilityRange(DateRange selectedRange, String availabilityType, Date maxDate) {
if (AVAILABILITY_WEEK.equals(availabilityType)) {
Date weekBeginDate = beginOfNaturalWeek(selectedRange.endDate);
Date weekEndDate = endOfNaturalWeek(selectedRange.endDate);
if (weekEndDate.after(maxDate)) {
// 为什么截到当前日期:自然周未结束时,不能把未来班次计入计划时间分母。
weekEndDate = maxDate;
}
return new DateRange(weekBeginDate, weekEndDate);
}
if (AVAILABILITY_TOTAL.equals(availabilityType)) {
return selectedRange;
}
// 为什么用页面结束日:日利用率是用户选择日期的生产日,不再固定取服务器“今天”。
return new DateRange(selectedRange.endDate, selectedRange.endDate);
}
private Date beginOfNaturalWeek(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(truncateToDay(date));
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
int mondayOffset = (dayOfWeek + 5) % 7;
cal.add(Calendar.DATE, -mondayOffset);
return cal.getTime();
}
private Date endOfNaturalWeek(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(beginOfNaturalWeek(date));
cal.add(Calendar.DATE, 6);
return cal.getTime();
}
private Date parseDay(String value, SimpleDateFormat sdf, Date defaultDate) {
if (!StringUtils.hasText(value)) {
return defaultDate;
@ -802,6 +1155,45 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
}
}
private static class QueryRange {
private final Date beginTime;
private Date endTime;
private QueryRange(Date beginTime, Date endTime) {
this.beginTime = beginTime;
this.endTime = endTime;
}
private Date getBeginTime() {
return beginTime;
}
}
private static class MetricCache {
private final Map<String, List<ParamRawPoint>> runRawPointsByDevice = new HashMap<>();
private final Map<String, List<ParamRawPoint>> shotRawPointsByDevice = new HashMap<>();
private final Map<String, BigDecimal> cavitiesByDevice = new HashMap<>();
private final Map<String, Map<String, Object>> cycleStatsByDevice = new HashMap<>();
private List<ParamRawPoint> getRunRawPoints(String deviceCode) {
return getRawPoints(runRawPointsByDevice, deviceCode);
}
private List<ParamRawPoint> getShotRawPoints(String deviceCode) {
return getRawPoints(shotRawPointsByDevice, deviceCode);
}
private List<ParamRawPoint> getRawPoints(Map<String, List<ParamRawPoint>> rawPointsByDevice,
String deviceCode) {
List<ParamRawPoint> rawPoints = rawPointsByDevice.get(deviceCode);
return rawPoints == null ? Collections.emptyList() : rawPoints;
}
private Map<String, Object> getCycleStats(String deviceCode) {
return cycleStatsByDevice.get(deviceCode);
}
}
private static class CycleResult {
private BigDecimal cycleSeconds = BigDecimal.ZERO;
private boolean estimated = true;

@ -4,6 +4,12 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aucma.report.mapper.InjectionOeeMapper">
<resultMap id="ParamRawPointResultMap" type="com.aucma.report.domain.vo.ParamRawPoint">
<result property="deviceCode" column="DEVICE_CODE"/>
<result property="collectTime" column="COLLECT_TIME"/>
<result property="paramValue" column="PARAM_VALUE"/>
</resultMap>
<!-- 1. 查询注塑车间设备列表 -->
<select id="selectTargetDevices" resultType="com.aucma.report.domain.vo.InjectionOeeAnalysisVo">
SELECT
@ -157,6 +163,59 @@
WHERE rn = 1
</select>
<!-- 3.1 批量获取设备最新模腔数,避免全设备 OEE 每台设备单独查一次 -->
<select id="selectLatestCavitiesBatch" resultType="java.util.HashMap">
WITH source_param AS (
<choose>
<when test="oldDevice">
SELECT v.DEVICE_CODE, v.PARAM_VALUE, v.COLLECT_TIME
FROM BASE_DEVICE_PARAM_VAL v
WHERE v.DEVICE_CODE IN
<foreach item="deviceCode" collection="deviceCodes" open="(" separator="," close=")">
#{deviceCode}
</foreach>
AND v.DEVICE_CODE LIKE 'OLD-%'
AND v.PARAM_NAME IN ('system.sv_iCavities', '机台状态-模腔数')
AND v.COLLECT_TIME &lt;= #{endTime}
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</when>
<when test="tableSuffixes != null and tableSuffixes.size() > 0">
<foreach item="suffix" collection="tableSuffixes" separator=" UNION ALL ">
SELECT v.DEVICE_CODE, v.PARAM_VALUE, v.COLLECT_TIME
FROM BASE_DEVICE_PARAM_VAL_${suffix} v
WHERE v.DEVICE_CODE IN
<foreach item="deviceCode" collection="deviceCodes" open="(" separator="," close=")">
#{deviceCode}
</foreach>
AND v.DEVICE_CODE NOT LIKE 'OLD-%'
AND v.PARAM_NAME IN ('system.sv_iCavities', '机台状态-模腔数')
AND v.COLLECT_TIME &lt;= #{endTime}
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</foreach>
</when>
<otherwise>
SELECT CAST(NULL AS VARCHAR2(100)) AS DEVICE_CODE,
CAST(NULL AS VARCHAR2(100)) AS PARAM_VALUE,
CAST(NULL AS DATE) AS COLLECT_TIME
FROM DUAL
WHERE 1 = 0
</otherwise>
</choose>
),
ranked_param AS (
SELECT DEVICE_CODE,
TO_NUMBER(TRIM(PARAM_VALUE)) AS CAVITIES,
ROW_NUMBER() OVER (
PARTITION BY DEVICE_CODE
ORDER BY COLLECT_TIME DESC
) AS rn
FROM source_param
)
SELECT DEVICE_CODE, CAVITIES
FROM ranked_param
WHERE rn = 1
</select>
<!-- 4. 获取窗口内正数周期时间采样列表 -->
<select id="selectCycleTimeSamples" resultType="java.math.BigDecimal">
WITH source_param AS (
@ -195,10 +254,118 @@
)
SELECT TO_NUMBER(TRIM(PARAM_VALUE))
FROM source_param
WHERE TO_NUMBER(TRIM(PARAM_VALUE)) &gt; 0
<!-- 为什么过滤 1~500 秒:周期时间偶发 0/极小值/超大脏值会把 P 拉偏OEE 只采用现场可解释样本。 -->
WHERE TO_NUMBER(TRIM(PARAM_VALUE)) &gt; 1000000
AND TO_NUMBER(TRIM(PARAM_VALUE)) &lt;= 500000000
ORDER BY COLLECT_TIME
</select>
<!-- 4.1 数据库端计算周期时间统计值,减少大量采样点回传和 Java 侧排序成本 -->
<select id="selectCycleTimeStats" resultType="java.util.HashMap">
WITH source_param AS (
<choose>
<when test="oldDevice">
SELECT v.PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL v
WHERE v.DEVICE_CODE = #{deviceCode}
AND v.DEVICE_CODE LIKE 'OLD-%'
AND v.PARAM_NAME = '机床实时参数-周期时间'
AND v.COLLECT_TIME &gt;= #{beginTime}
AND v.COLLECT_TIME &lt; #{endTime}
<!-- 为什么仍保留正则Oracle 会先解析表达式,提前过滤可避免 TO_NUMBER 因脏值中断整页报表。 -->
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</when>
<when test="tableSuffixes != null and tableSuffixes.size() > 0">
<foreach item="suffix" collection="tableSuffixes" separator=" UNION ALL ">
SELECT v.PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL_${suffix} v
WHERE v.DEVICE_CODE = #{deviceCode}
AND v.DEVICE_CODE NOT LIKE 'OLD-%'
AND v.PARAM_NAME = '机床实时参数-周期时间'
AND v.COLLECT_TIME &gt;= #{beginTime}
AND v.COLLECT_TIME &lt; #{endTime}
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</foreach>
</when>
<otherwise>
SELECT CAST(NULL AS VARCHAR2(100)) AS PARAM_VALUE
FROM DUAL
WHERE 1 = 0
</otherwise>
</choose>
),
numeric_param AS (
SELECT TO_NUMBER(TRIM(PARAM_VALUE)) / 1000000 AS CYCLE_SECONDS
FROM source_param
<!-- 为什么过滤 1~500 秒:中位数要抗极端值,但不能让明显脏样本进入候选集合。 -->
WHERE TO_NUMBER(TRIM(PARAM_VALUE)) &gt; 1000000
AND TO_NUMBER(TRIM(PARAM_VALUE)) &lt;= 500000000
)
SELECT COUNT(1) AS SAMPLE_COUNT,
AVG(CYCLE_SECONDS) AS AVG_SECONDS,
STDDEV(CYCLE_SECONDS) AS STDDEV_SECONDS,
MEDIAN(CYCLE_SECONDS) AS MEDIAN_SECONDS
FROM numeric_param
</select>
<!-- 4.2 批量计算周期时间统计值:一次 SQL 覆盖设备集合,减少全设备报表的往返次数 -->
<select id="selectCycleTimeStatsBatch" resultType="java.util.HashMap">
WITH source_param AS (
<choose>
<when test="oldDevice">
SELECT v.DEVICE_CODE, v.PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL v
WHERE v.DEVICE_CODE IN
<foreach item="deviceCode" collection="deviceCodes" open="(" separator="," close=")">
#{deviceCode}
</foreach>
AND v.DEVICE_CODE LIKE 'OLD-%'
AND v.PARAM_NAME = '机床实时参数-周期时间'
AND v.COLLECT_TIME &gt;= #{beginTime}
AND v.COLLECT_TIME &lt; #{endTime}
<!-- 为什么仍保留正则Oracle 会先解析表达式,提前过滤可避免 TO_NUMBER 因脏值中断整页报表。 -->
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</when>
<when test="tableSuffixes != null and tableSuffixes.size() > 0">
<foreach item="suffix" collection="tableSuffixes" separator=" UNION ALL ">
SELECT v.DEVICE_CODE, v.PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL_${suffix} v
WHERE v.DEVICE_CODE IN
<foreach item="deviceCode" collection="deviceCodes" open="(" separator="," close=")">
#{deviceCode}
</foreach>
AND v.DEVICE_CODE NOT LIKE 'OLD-%'
AND v.PARAM_NAME = '机床实时参数-周期时间'
AND v.COLLECT_TIME &gt;= #{beginTime}
AND v.COLLECT_TIME &lt; #{endTime}
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</foreach>
</when>
<otherwise>
SELECT CAST(NULL AS VARCHAR2(100)) AS DEVICE_CODE,
CAST(NULL AS VARCHAR2(100)) AS PARAM_VALUE
FROM DUAL
WHERE 1 = 0
</otherwise>
</choose>
),
numeric_param AS (
SELECT DEVICE_CODE,
TO_NUMBER(TRIM(PARAM_VALUE)) / 1000000 AS CYCLE_SECONDS
FROM source_param
<!-- 为什么过滤 1~500 秒:与单设备算法保持一致,避免某台设备的脏周期值污染 P。 -->
WHERE TO_NUMBER(TRIM(PARAM_VALUE)) &gt; 1000000
AND TO_NUMBER(TRIM(PARAM_VALUE)) &lt;= 500000000
)
SELECT DEVICE_CODE,
COUNT(1) AS SAMPLE_COUNT,
AVG(CYCLE_SECONDS) AS AVG_SECONDS,
STDDEV(CYCLE_SECONDS) AS STDDEV_SECONDS,
MEDIAN(CYCLE_SECONDS) AS MEDIAN_SECONDS
FROM numeric_param
GROUP BY DEVICE_CODE
</select>
<!-- 5. 按时间窗口统计去重后的质检记录 -->
<select id="selectQualitySummary" resultType="java.util.HashMap">
WITH bar_defect AS (
@ -235,4 +402,102 @@
)
</select>
<!-- 7. 批量查询设备在一个有效时间段内的原始去重参数值列表(以 COLLECT_TIME 升序排列) -->
<select id="selectRawParamValues" resultMap="ParamRawPointResultMap">
WITH source_param AS (
<choose>
<when test="oldDevice">
SELECT v.DEVICE_CODE AS DEVICE_CODE,
v.COLLECT_TIME AS COLLECT_TIME,
TO_NUMBER(TRIM(v.PARAM_VALUE)) AS PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL v
WHERE v.DEVICE_CODE = #{deviceCode}
AND v.DEVICE_CODE LIKE 'OLD-%'
AND v.PARAM_NAME = #{paramName}
AND v.COLLECT_TIME &gt;= #{minQueryBegin}
AND v.COLLECT_TIME &lt; #{maxQueryEnd}
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</when>
<when test="tableSuffixes != null and tableSuffixes.size() > 0">
<foreach item="suffix" collection="tableSuffixes" separator=" UNION ALL ">
SELECT v.DEVICE_CODE AS DEVICE_CODE,
v.COLLECT_TIME AS COLLECT_TIME,
TO_NUMBER(TRIM(v.PARAM_VALUE)) AS PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL_${suffix} v
WHERE v.DEVICE_CODE = #{deviceCode}
AND v.DEVICE_CODE NOT LIKE 'OLD-%'
AND v.PARAM_NAME = #{paramName}
AND v.COLLECT_TIME &gt;= #{minQueryBegin}
AND v.COLLECT_TIME &lt; #{maxQueryEnd}
<!-- 为什么先正则校验:避免 PARAM_VALUE 中脏字符导致 TO_NUMBER 转换抛异常中断整个报表 -->
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</foreach>
</when>
<otherwise>
SELECT CAST(NULL AS VARCHAR2(100)) AS DEVICE_CODE,
CAST(NULL AS DATE) AS COLLECT_TIME,
CAST(NULL AS NUMBER) AS PARAM_VALUE
FROM DUAL
WHERE 1 = 0
</otherwise>
</choose>
)
SELECT DEVICE_CODE, COLLECT_TIME, MAX(PARAM_VALUE) AS PARAM_VALUE
FROM source_param
GROUP BY DEVICE_CODE, COLLECT_TIME
ORDER BY COLLECT_TIME
</select>
<!-- 7.1 批量查询设备集合的原始去重参数值Service 按 DEVICE_CODE 分桶后复用同一批数据 -->
<select id="selectRawParamValuesBatch" resultMap="ParamRawPointResultMap">
WITH source_param AS (
<choose>
<when test="oldDevice">
SELECT v.DEVICE_CODE AS DEVICE_CODE,
v.COLLECT_TIME AS COLLECT_TIME,
TO_NUMBER(TRIM(v.PARAM_VALUE)) AS PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL v
WHERE v.DEVICE_CODE IN
<foreach item="deviceCode" collection="deviceCodes" open="(" separator="," close=")">
#{deviceCode}
</foreach>
AND v.DEVICE_CODE LIKE 'OLD-%'
AND v.PARAM_NAME = #{paramName}
AND v.COLLECT_TIME &gt;= #{minQueryBegin}
AND v.COLLECT_TIME &lt; #{maxQueryEnd}
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</when>
<when test="tableSuffixes != null and tableSuffixes.size() > 0">
<foreach item="suffix" collection="tableSuffixes" separator=" UNION ALL ">
SELECT v.DEVICE_CODE AS DEVICE_CODE,
v.COLLECT_TIME AS COLLECT_TIME,
TO_NUMBER(TRIM(v.PARAM_VALUE)) AS PARAM_VALUE
FROM BASE_DEVICE_PARAM_VAL_${suffix} v
WHERE v.DEVICE_CODE IN
<foreach item="deviceCode" collection="deviceCodes" open="(" separator="," close=")">
#{deviceCode}
</foreach>
AND v.DEVICE_CODE NOT LIKE 'OLD-%'
AND v.PARAM_NAME = #{paramName}
AND v.COLLECT_TIME &gt;= #{minQueryBegin}
AND v.COLLECT_TIME &lt; #{maxQueryEnd}
<!-- 为什么先正则校验:避免 PARAM_VALUE 中脏字符导致 TO_NUMBER 转换抛异常中断整个报表 -->
AND REGEXP_LIKE(TRIM(v.PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
</foreach>
</when>
<otherwise>
SELECT CAST(NULL AS VARCHAR2(100)) AS DEVICE_CODE,
CAST(NULL AS DATE) AS COLLECT_TIME,
CAST(NULL AS NUMBER) AS PARAM_VALUE
FROM DUAL
WHERE 1 = 0
</otherwise>
</choose>
)
SELECT DEVICE_CODE, COLLECT_TIME, MAX(PARAM_VALUE) AS PARAM_VALUE
FROM source_param
GROUP BY DEVICE_CODE, COLLECT_TIME
ORDER BY DEVICE_CODE, COLLECT_TIME
</select>
</mapper>

Loading…
Cancel
Save