select sum(z.illuminance) as illuminance,sum(z.airtemperature) as airtemperature,sum(z.totlerainfall) as totlerainfall from dwd_iot_weather_station_di z where 1 = 1 <if test="param.deviceId != null"> AND z.device_id = #{param.deviceId} </if> AND (TO_CHAR(z.data_date, 'MM-DD') BETWEEN '01-01' AND #{param.statDateEnd}) AND EXTRACT(YEAR FROM z.data_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) AND EXTRACT(YEAR FROM CURRENT_DATE);
时间: 2025-07-21 12:00:17 浏览: 9
<think>我们注意到原始查询中使用了`TO_CHAR(z.data_date, 'MM-DD')`来比较日期范围,并且年份范围被固定为当前年份(起止都是当前年份)。但是,用户要求查询的是从年初到指定结束日期(可能是跨年日期)的数据,而当前写法只能查询当前年份内的数据。如果结束日期是跨年的(比如2023-01-01到2024-01-15),则当前查询会遗漏前一年(2023年)的数据。
因此,我们需要调整查询逻辑:
1. 不再固定年份范围,而是通过`TO_CHAR(z.data_date, 'MM-DD')`在每年范围内比较月份和日期。
2. 但是,这样会带来另一个问题:如果结束日期是'02-29',那么非闰年就会没有29号,所以需要谨慎。
然而,用户的需求可能是:统计从每年1月1日到结束日期(比如每年统计到2月28日)的数据,并且要包含所有年份(但原始查询中只查了当前年份)。根据问题描述,我们需要的是四条记录:去年记录、最近3年平均、最近5年平均、最近10年平均。
所以,我们需要重构查询,分别计算去年、最近3年、5年、10年的每年从1月1日到指定结束日期的累计值,然后分别取平均值(除了去年是单年值)。
步骤:
1. 计算每一年的累计值:从当年1月1日到当年的结束日期(结束日期由参数`param.statDateEnd`给出,格式为'MM-DD')的累计值。
2. 然后根据不同的时间范围(去年、最近3年、最近5年、最近10年)进行聚合。
注意:结束日期可能跨年?但这里我们只考虑当年内的结束日期(即结束日期在当年内)。
修改后的SQL思路:
我们将分别计算四个部分(去年、3年平均、5年平均、10年平均)的累计值。每个部分都需要先按年分组计算每年的累计值,然后再进行聚合(去年直接取,其他取平均)。
但是,由于需要四个不同的时间范围,我们可以先计算每个年份的累计值(子查询),然后通过外层查询分别聚合不同的时间范围。
具体步骤:
1. 子查询:计算每个年份(在所需范围内)从1月1日到指定结束日期的累计值。
- 条件:`TO_CHAR(z.data_date, 'MM-DD') <= #{param.statDateEnd}`
- 注意:这里我们不需要下界,因为是从1月1日开始,所以只需要小于等于结束日期。
- 年份范围:我们需要考虑从(当前年份-10)到(当前年份-1)的年份(因为去年是当前年份-1,而最近3年、5年、10年都是到去年为止)。
2. 外层查询:
- 去年记录:取年份=当前年份-1的累计值
- 最近3年平均:取年份在[当前年份-3, 当前年份-1]的累计值的平均值
- 最近5年平均:取年份在[当前年份-5, 当前年份-1]的累计值的平均值
- 最近10年平均:取年份在[当前年份-10, 当前年份-1]的累计值的平均值
3. 注意:如果某一年没有数据,则不会计入平均值(即AVG函数会自动忽略NULL)。
4. 由于原查询是求和(sum),所以我们需要在子查询中按年求和。
5. 参数`param.statDateEnd`是结束日期的月份和日期(如'02-28')。
6. 年份范围:因为要计算去年,所以去年是当前年份-1;最近3年是前三年(不包括今年)。
7. 当前年份使用`EXTRACT(YEAR FROM CURRENT_DATE)`。
编写SQL:
首先,我们构建一个子查询,计算每个年份的累计值:
```sql
SELECT
EXTRACT(YEAR FROM z.data_date) AS year,
SUM(z.illuminance) AS illuminance_sum,
SUM(z.airtemperature) AS airtemperature_sum,
SUM(z.totlerainfall) AS totlerainfall_sum
FROM dwd_iot_weather_station_di z
WHERE 1=1
<if test="param.deviceId != null">
AND z.device_id = #{param.deviceId}
</if>
-- 只取每年1月1日到结束日期(同一年内)的数据
AND TO_CHAR(z.data_date, 'MM-DD') <= #{param.statDateEnd}
-- 年份范围:从当前年份-10到当前年份-1(因为我们不需要今年的数据,今年还没过完?但根据需求,我们只取到去年)
AND EXTRACT(YEAR FROM z.data_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 10 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
GROUP BY EXTRACT(YEAR FROM z.data_date)
```
然后,我们使用这个子查询的结果,分别计算四条记录:
注意:由于我们可能需要多个指标(illuminance, airtemperature, totlerainfall),所以我们需要分别对这三个指标进行聚合。
我们可以用多个SELECT + UNION ALL来组合,但每个指标我们都需要输出三条记录(去年、3年平均、5年平均、10年平均)?不,我们需要同时输出三个指标的四条记录(每条记录包含三个指标的值)。
但是,问题要求输出四条记录,每条记录对应一个类型(去年记录、最近3年平均等),并且每个类型包含三个指标的值。
因此,我们不需要UNION ALL,而是应该在一个查询中输出一行,每行包含四个字段(record_type, illuminance, airtemperature, totlerainfall)?但是这样四条记录是独立的,所以还是需要UNION ALL四个部分。
然而,四个部分都是对同一个子查询结果的不同过滤和聚合,所以我们可以这样:
方案一:使用多个子查询连接(但这里我们使用UNION ALL分别计算四个部分)
方案二:使用一个外层查询,通过条件聚合(CASE WHEN)来分别计算四个部分。但是这样输出会是一行,而我们需要四条记录。所以还是用UNION ALL。
我们使用UNION ALL来组合四个部分:
第一部分:去年记录
```sql
SELECT
'去年记录' AS record_type,
y.illuminance_sum AS illuminance,
y.airtemperature_sum AS airtemperature,
y.totlerainfall_sum AS totlerainfall
FROM yearly_sums y
WHERE y.year = EXTRACT(YEAR FROM CURRENT_DATE) - 1
```
第二部分:最近3年平均
```sql
SELECT
'最近3年平均' AS record_type,
AVG(y.illuminance_sum) AS illuminance,
AVG(y.airtemperature_sum) AS airtemperature,
AVG(y.totlerainfall_sum) AS totlerainfall
FROM yearly_sums y
WHERE y.year BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 3 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
```
第三部分:最近5年平均
```sql
SELECT
'最近5年平均' AS record_type,
AVG(y.illuminance_sum) AS illuminance,
AVG(y.airtemperature_sum) AS airtemperature,
AVG(y.totlerainfall_sum) AS totlerainfall
FROM yearly_sums y
WHERE y.year BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 5 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
```
第四部分:最近10年平均
```sql
SELECT
'最近10年平均' AS record_type,
AVG(y.illuminance_sum) AS illuminance,
AVG(y.airtemperature_sum) AS airtemperature,
AVG(y.totlerainfall_sum) AS totlerainfall
FROM yearly_sums y
WHERE y.year BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 10 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
```
组合起来,使用WITH子句:
完整SQL:
```sql
WITH yearly_sums AS (
SELECT
EXTRACT(YEAR FROM z.data_date) AS year,
SUM(z.illuminance) AS illuminance_sum,
SUM(z.airtemperature) AS airtemperature_sum,
SUM(z.totlerainfall) AS totlerainfall_sum
FROM dwd_iot_weather_station_di z
WHERE 1=1
<if test="param.deviceId != null">
AND z.device_id = #{param.deviceId}
</if>
AND TO_CHAR(z.data_date, 'MM-DD') <= #{param.statDateEnd}
AND EXTRACT(YEAR FROM z.data_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 10 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
GROUP BY EXTRACT(YEAR FROM z.data_date)
)
-- 去年记录
SELECT
'去年记录' AS record_type,
y.illuminance_sum AS illuminance,
y.airtemperature_sum AS airtemperature,
y.totlerainfall_sum AS totlerainfall
FROM yearly_sums y
WHERE y.year = EXTRACT(YEAR FROM CURRENT_DATE) - 1
UNION ALL
-- 最近3年平均
SELECT
'最近3年平均' AS record_type,
AVG(y.illuminance_sum) AS illuminance,
AVG(y.airtemperature_sum) AS airtemperature,
AVG(y.totlerainfall_sum) AS totlerainfall
FROM yearly_sums y
WHERE y.year BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 3 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
UNION ALL
-- 最近5年平均
SELECT
'最近5年平均' AS record_type,
AVG(y.illuminance_sum) AS illuminance,
AVG(y.airtemperature_sum) AS airtemperature,
AVG(y.totlerainfall_sum) AS totlerainfall
FROM yearly_sums y
WHERE y.year BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 5 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
UNION ALL
-- 最近10年平均
SELECT
'最近10年平均' AS record_type,
AVG(y.illuminance_sum) AS illuminance,
AVG(y.airtemperature_sum) AS airtemperature,
AVG(y.totlerainfall_sum) AS totlerainfall
FROM yearly_sums y
WHERE y.year BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 10 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1;
```
注意:在平均值的部分,我们使用了AVG函数,如果某一年份没有数据,则不会计入平均值(即NULL会被忽略)。
但是,这里有一个问题:在去年的部分,如果去年没有数据,则不会返回任何记录。而其他平均部分如果没有数据,则返回NULL(或者不返回行)。为了确保四条记录都出现,我们可以使用COALESCE或者确保子查询中包含这些年份?但子查询中已经过滤了年份范围,如果年份范围内没有数据,那么就不会有该年的数据,所以平均部分可能没有行。我们可以使用类似的方法,在外层查询中生成年份,然后左连接?但这样会复杂。
根据需求,我们可能需要即使没有数据也要显示0或者NULL。但原需求没有特别说明,我们按上述方式处理。
另外,注意在MyBatis中,参数使用`#{param.statDateEnd}`,需要确保传入的格式是'MM-DD'。
最后,我们输出四条记录,每条记录包含四个字段:record_type, illuminance, airtemperature, totlerainfall。
示例输出:
| record_type | illuminance | airtemperature | totlerainfall |
|-----------------|-------------|----------------|---------------|
| 去年记录 | 1200 | 3500 | 150 |
| 最近3年平均 | 1100 | 3400 | 140 |
| 最近5年平均 | 1000 | 3300 | 130 |
| 最近10年平均 | 900 | 3200 | 120 |
注意:平均值可能是小数,如果需要整数,可以四舍五入(如使用ROUND函数)。
根据用户要求,我们只查询四条记录,并且分别是去年记录、最近3年平均、最近5年平均、最近10年平均。
因此,这个SQL应该满足要求。
但是,原查询中有一个条件:`AND EXTRACT(YEAR FROM z.data_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) AND EXTRACT(YEAR FROM CURRENT_DATE)`,这是只取当前年份,而我们修改后取的是当前年份-10到当前年份-1(即过去10年,不包括今年)。
这符合需求,因为去年记录是当前年份-1,而最近3年、5年、10年都不包括今年(因为今年还没过完,且我们统计的是到某个结束日期,今年可能还没到那个结束日期)。
所以,这个SQL是合理的。
但是,请注意:如果结束日期是12-31,那么今年是否应该包括?但需求是四条记录都不包括今年,所以没有问题。
最后,我们按照要求输出。</think>### 修正后的SQL(移除年份范围错误)
```sql
SELECT
'去年记录' AS record_type,
SUM(z.illuminance) AS illuminance,
SUM(z.airtemperature) AS airtemperature,
SUM(z.totlerainfall) AS totlerainfall
FROM dwd_iot_weather_station_di z
WHERE 1 = 1
<if test="param.deviceId != null">
AND z.device_id = #{param.deviceId}
</if>
AND TO_CHAR(z.data_date, 'MM-DD') BETWEEN '01-01' AND #{param.statDateEnd}
AND EXTRACT(YEAR FROM z.data_date) = EXTRACT(YEAR FROM CURRENT_DATE) - 1
UNION ALL
SELECT
'最近3年平均' AS record_type,
AVG(illuminance_sum),
AVG(airtemperature_sum),
AVG(totlerainfall_sum)
FROM (
SELECT
EXTRACT(YEAR FROM data_date) AS year,
SUM(illuminance) AS illuminance_sum,
SUM(airtemperature) AS airtemperature_sum,
SUM(totlerainfall) AS totlerainfall_sum
FROM dwd_iot_weather_station_di
WHERE 1 = 1
<if test="param.deviceId != null">
AND device_id = #{param.deviceId}
</if>
AND TO_CHAR(data_date, 'MM-DD') BETWEEN '01-01' AND #{param.statDateEnd}
AND EXTRACT(YEAR FROM data_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 3 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
GROUP BY EXTRACT(YEAR FROM data_date)
) sub
UNION ALL
SELECT
'最近5年平均' AS record_type,
AVG(illuminance_sum),
AVG(airtemperature_sum),
AVG(totlerainfall_sum)
FROM (
SELECT
EXTRACT(YEAR FROM data_date) AS year,
SUM(illuminance) AS illuminance_sum,
SUM(airtemperature) AS airtemperature_sum,
SUM(totlerainfall) AS totlerainfall_sum
FROM dwd_iot_weather_station_di
WHERE 1 = 1
<if test="param.deviceId != null">
AND device_id = #{param.deviceId}
</if>
AND TO_CHAR(data_date, 'MM-DD') BETWEEN '01-01' AND #{param.statDateEnd}
AND EXTRACT(YEAR FROM data_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 5 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
GROUP BY EXTRACT(YEAR FROM data_date)
) sub
UNION ALL
SELECT
'最近10年平均' AS record_type,
AVG(illuminance_sum),
AVG(airtemperature_sum),
AVG(totlerainfall_sum)
FROM (
SELECT
EXTRACT(YEAR FROM data_date) AS year,
SUM(illuminance) AS illuminance_sum,
SUM(airtemperature) AS airtemperature_sum,
SUM(totlerainfall) AS totlerainfall_sum
FROM dwd_iot_weather_station_di
WHERE 1 = 1
<if test="param.deviceId != null">
AND device_id = #{param.deviceId}
</if>
AND TO_CHAR(data_date, 'MM-DD') BETWEEN '01-01' AND #{param.statDateEnd}
AND EXTRACT(YEAR FROM data_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 10 AND EXTRACT(YEAR FROM CURRENT_DATE) - 1
GROUP BY EXTRACT(YEAR FROM data_date)
) sub;
```
### 关键修正说明:
1. **年份范围错误修复**:
- 原查询:`BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) AND EXTRACT(YEAR FROM CURRENT_DATE)` 错误(只查当前年份)
- 修正后:
- 去年记录:`= EXTRACT(YEAR FROM CURRENT_DATE) - 1`
- 平均记录:`BETWEEN [起始年] AND EXTRACT(YEAR FROM CURRENT_DATE) - 1`
2. **多级聚合结构**:
- 平均记录使用子查询先按年求和,再计算平均值
- 避免直接对多年数据求和导致错误
3. **时间范围说明**:
| 记录类型 | 年份范围 | 说明 |
|----------------|------------------------------------------|-----------------------|
| 去年记录 | 当前年份-1 | 单年数据 |
| 最近3年平均 | [当前年份-3, 当前年份-1] | 3年数据的平均值 |
| 最近5年平均 | [当前年份-5, 当前年份-1] | 5年数据的平均值 |
| 最近10年平均 | [当前年份-10, 当前年份-1] | 10年数据的平均值 |
4. **日期过滤优化**:
- 使用`TO_CHAR(data_date, 'MM-DD')`确保每年都过滤相同日期范围
- 不受具体年份影响,只比较月份和日期
### 注意事项:
1. 当前年份计算:`EXTRACT(YEAR FROM CURRENT_DATE)`
2. 结束日期参数:`#{param.statDateEnd}` 需传入'MM-DD'格式
3. 在Oracle中可能需要使用`TO_CHAR()`,MySQL中使用`DATE_FORMAT()`
4. 空值处理:无数据的年份不会影响平均值计算
阅读全文
相关推荐




















