MySQL遇到的一些坑

code-jia / 2024-03-13 / 原文

第一大坑

后端代码没问题,前端拉取代码的时候拉取的是master分支,而master分支还没有跟新到最新进度,导致某些菜单访问正常但是有几个菜单模块访问不了,浏览器进度条卡住

后端也要改,可以重新拉取或者merge,后端和前端要对应上

git pull http://117.73.11.3:8090/WindPowerPrediction-server/wpp-web.git zz_develop
git pull http://117.73.11.3:8090/WindPowerPrediction-server/wpp-server.git zz_serve_develop

建议:这里建议git命令拉取代码的时候,指定分支,否则默认master

写sql

为什么表名表字段没有提示

为什么字段对应了还要取别名才对

ONLY_FUll_GROUP_BY的意思是:对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY语句中,也就是说查出来的列必须是GROUP BY之后的字段,或者这个字段出现在聚合函数里面

分组时只按time分组会报错,因为启用了 ONLY_FULL_GROUP_BY,count是聚合函数可以不用

    <select id="predictedStatistics" resultType="com.wpp.platform.predictedData.domain.PredictedDataDto">
		select p.predicted_power as predictedPower,p.time,sum(p.predicted_power) as count
		from wpp_prediction_data p
		where p.type = #{type} and p.device_id = #{deviceId}
		group by p.time, p.predicted_power
		order by p.time;
	</select>

查询出来是一组null组成的List

或者是 数据库查询没问题,但是前端/代码中service层或者controller层打印出来有的参数是null, 这说明问题出在Mapper层,没有自定义映射—parameterType和resultMap

多次踩坑

原因是:首先,参数写错了…返回的结果List泛型是实体类,疏忽写成了实体类对应的DTO,(DTO是用来接收前端传过来的参数的)

然后Mapper.xml文件没有自定义映射结果集,而是使用的resultType,应该使用resultMap,参数类型也写上parameterType

月报年报统计

// 将日期格式化为年月日,去掉时间部分
DATE(`time`)
// 将日期格式化为想要的格式
DATE_FORMAT(`time`, '%Y-%m')
// 但是format之后是字符串,使用CAST转换一下
CAST(DATE_FORMAT(`time`, '%Y-%m') AS DATETIME) AS `time`,

月报统计时间查询不出来

大坑

错误使用:

这里需要对数据进行按月聚合统计,所以需要对时间进行处理将时间格式化为年-月,然后取别名为time,耽耽耽是原始表中的时间列也叫time,直接使用time进行group by会默认使用原始表的故而不会按月聚合,所以group by之后的时间使用DATE_FORMAT(time, '%Y-%m')

不要使用CAST转换:CAST(DATE_FORMAT(time, '%Y-%m') AS DATETIME)

有时候又报不能转字符串到Date…


同理,当下面查询被当作子查询时,直接写select time会出错,可以写表名.

image-20240125145810488

处理后:

image-20240125145742756

mybatis映射的时候,比如我查出来count在映射map 中添加一行映射count到实体类,但是这个count是Double型的,我在实体类添加了一个Integer整形的字段,那么它不会报错也不会将count映射出来,因为方法参数不对,通过setter方法

数据库时间能查出来2024-01精确到月份,但是mybatis查出time为null,通过在实体类添加一个String类型字段timeMonth,在自定义映射中将timeMonth进行映射,最后在业务层处理将其赋值给time属性

SQL

count(*)

select count() from table括号内可以是也可以是列名,当然不是列名不行哦

COUNT(*) 统计数据行数,COUNT(column) 统计column非NULL的行数.

limit 的两种格式:

limit 2, 5 第一个参数表示从第2条数据开始,第二个参数表示往后截取5条数据

limit 6 offset 3 很明显第二个参数是表示偏移量,从第3开始截取6个数据

limit 3 截取3个数据,偏移量省略默认为0

当我在controller层封装一个对象参数,但是我有检查字段是否非空的需求时,首先想到@requestParam(required=false),但是这是对于单个参数,不能用于对象,且如果使用单个参数配注解,会使得参数列表很长影响可读性,所以可以使用JSR303校验,在参数对象的实体类字段上加上@NotBlank注解,在参数使用前面加上注解@Valid

sql计算差值聚合

image-20240130142624702

超短期精度 = 1 - |一天的实际功率求和 - 预测功率求和|/实际功率求和

按天聚合,计算绝对值,除法,减法

// 按天聚合查询实际功率的sum和时间
SELECT DATE(t.`time`) as time, SUM(t.actual_power_output) as actual_power_output from wpp_wind_turbines_data as t 
				where t.device_id = 217 and DATE(t.`time`) BETWEEN '2024-01-01' and '2024-12-31'
				GROUP BY DATE(t.`time`)

最后的sql长这样:

// 将预测表中的时间设备id和预测功率查出来,join风电机组表的实际功率时间和设备id,依据时间和设备id连接,
// 再将这个连接表作为表查询计算比值
SELECT 
	fir.time as time, fir.device_id as device_id,   
	ROUND(1 - ABS(s.actual_power_output - fir.predicted_power) / s.actual_power_output, 2) as rate,
	ROUND(1 - ABS(s.actual_power_output - fir.reported_power) / s.actual_power_output, 2) as report_rate,
	fir.count as count
	from 
(SELECT 
			DATE(p.`time`) AS time,
			p.device_id as device_id,
			SUM(p.predicted_power) as predicted_power,
			SUM(p.reported_power) as reported_power,
			count(*) as count
			
		FROM 
						wpp_reported_data as p
		WHERE
		p.`time` BETWEEN '2024-01-01 00:00:00.0' AND '2024-12-31 23:59:59.0'
					 AND p.device_id = 217
		GROUP BY
			DATE(p.`time`), p.device_id
		) as fir	
			left join 
			(
			SELECT DATE(t.`time`) as time, t.device_id as device_id, SUM(t.actual_power_output) as actual_power_output from wpp_wind_turbines_data as t 
				where t.device_id = 217 and DATE(t.`time`) BETWEEN '2024-01-01' and '2024-12-31'
				GROUP BY DATE(t.`time`)
			) as s on s.time = fir.time and fir.device_id = s.device_id
			

sql子查询妙用

blog

valueOf() 和 parseDouble()

  1. valueOf参数可以是字符串和double型,返回的是Double,parseDouble只能是字符串,返回的是double

占位符:

​ 三个占位符—整型int,long用%d

​ 浮点型float,double用%s

​ 字符串使用%s

DATE_FORMAT(t.time,'%Y-%m') as timeMonth,此处使用timeMonth接收转换后的时间,使用Stirng类型接收。因为time字段为Date类型,转换为年-月格式的字符串不能转为时间戳,所以会报错2024-01不能转为timestamp;

这里使用timeMonth中间量接收在业务层将其重新赋给time即可

注意:只有年报,以月格式才需要,如果是DATE(t.time) time,不需要,因为年-月-日格式可以转为timestamp

SELECT
			DATE_FORMAT(t.time,'%Y-%m') as timeMonth,
			t.device_id deviceId,
			CAST(ROUND( AVG( t.speed ), 2 ) AS CHAR) AS speed,
			CAST(ROUND( AVG( t.direction_angle ), 2 ) AS CHAR) AS directionAngle,
			CAST(ROUND( AVG( t.ideal_power ), 2 ) AS CHAR) AS idealPower,
			CAST(ROUND( AVG( t.available_power ), 2 ) AS CHAR) AS availablePower,
			CAST(ROUND( AVG( t.actual_power_output ), 2 ) AS CHAR) AS actualPowerOutput,
			CAST(ROUND( AVG( t.capacity_power ), 2 ) AS CHAR) AS capacityPower,
			CAST(ROUND( AVG( k.predicted_power ), 2 ) AS CHAR) AS predictedPower,
		     (
		         select direction
				 from wpp_wind_turbines_data s
		         WHERE date_format(s.time,'%Y-%m') = max(DATE_FORMAT(t.time,'%Y-%m')) AND device_id = #{deviceId}
				 group by direction
				 order by count(direction) desc
				 limit 1
		    ) direction

		FROM wpp_wind_turbines_data t
			left join wpp_prediction_data k on t.device_id =k.device_id and DATE_FORMAT(t.time,'%Y-%m') =DATE_FORMAT(k.time,'%Y-%m')
		WHERE date_format(t.time,'%Y') = #{curretDate} AND t.device_id = #{deviceId}
		GROUP BY DATE_FORMAT(t.time,'%Y-%m'),t.device_id

LocalDate和Date

Date过时了,因为其可读性差,转换麻烦而且某些方法线程不安全;所以建议使用Java8新特性LocalDate,在SpringBoot中应用

接口和接口默认方法

public interface Iterable<T> {

    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

解释:接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

我们常用的forEach方法就是一个defualt方法,有自己的实现,类实现此接口时不需要去实现forEach方法

try-catch

在做完整性统计分析,统计数据精度的时候,由于没有数据有的数据没有值,在解析的时候报空指针异常,解决方案是使用try-catch捕获异常,在finally里执行赋值vo的操作,如果值为null也会被try掉,然后继续执行

需要注意的是:

  1. 在catch块里不能再throw new 异常;
  2. 且catch的异常范围必须涵盖错误异常,否则捕获不到;

mybatis的标签判断参数是否存在或等于时,如果是数字1或‘1’都是表示数字

需要的数据:设备名字,设备坐标,设备状态,设备风速 风向 实时功率


2024年3月1日 星期五

  1. Springboot项目部署
    项目测试没问题之后打包,找到jar包 -> 拖动到MobaXterm根目录 -> 命令后台启动nohup java -jar ***.jar >/data/log.log 2>/data/err.log &
    前端项目build之后拖动到nginx目录下,前端无需启动

  2. 插入数据和更新数据等返回值int,useGeneratedKeys的妙用,会直接将id给到入参,不需要再查数据库获得id

  3. sql优化
    子查询耗时非常严重,一条sql查询耗时五六秒,优化后做到耗时0.6s

  4. 远程调用为什么要序列化
    远程调用序列化首先是因为在网络上传输需要使用二进制流形式,再者序列化之后语言无关,反序列提供便捷
    将 Student 对象保存到文件,保存到文件的数据是二进制数据,所以并不是说序列化只用在网络传输场景里,只要是保存数据的场景只能是二进制数据时,就需要用序列化。

  5. 插入数据重复主键则修改ON DUPLICATE KEY UPDATE
    适合批量插入时,但又出现重复主键错误时使用

    <insert id="insertPredictedDataHavingId">
    		INSERT INTO wpp_prediction_data (
    			device_id,
    			time,
    			predicted_power,
    			is_reported,
    			lower_confidence_limit,
    			upper_confidence_limit,
    			create_time
    		) VALUES
    		<foreach collection="list" item="item" index="index" separator=",">
    		(
    		          #{item.deviceId},
    				  #{item.time},
    				  #{item.predictedPower},
    				  #{item.isReported},
    				  #{item.lowerConfidenceLimit},
    				  #{item.upperConfidenceLimit},
    				  <![CDATA[now()]]>
    			)
    		</foreach>
    		ON DUPLICATE KEY UPDATE device_id = VALUES(device_id), time = VALUES(time), predicted_power = VALUES(predicted_power), is_reported = VALUES(is_reported),
    			                    lower_confidence_limit = VALUES(lower_confidence_limit), upper_confidence_limit = VALUES(upper_confidence_limit),create_time= VALUES(create_time);
    	</insert>
    

    2024年3月5日 星期二

    1. 分页查询
      离谱错误:getWindMeasurementTowerDataList()刚开始没有(),导致在测风塔页面点击分页页码时,不触发函数,但是页数页码传值准确,需要手动再点击查询

       <pagination
            v-show="total>0"
            :total="total"
            :page.sync="queryParams.pageNum"
            :limit.sync="queryParams.pageSize"
            @pagination="queryParams.selectDataSource == 0?getDeviceDataList():queryParams.selectDataSource == 1?getWindMeasurementTowerDataList():''"
          />
      
    2. 前端首次刷新不传值
      在前端页面钩子函数调用时,方法是异步执行的,同时执行,也就是不保证钩子函数里的三个方法执行顺序,导致后续有一个查询参数在前端控制台显示有值,但在异步调用后端接口时没有值
      应该在getMeasurementArchiveList方法里调用getList这种嵌套方法的格式保证方法顺序执行

      created() {
          this.setTime();
          this.getMeasurementArchiveList();
          this.getList();
        },
      
    3. SQL查询

      time between '2024-02-04' and '2024-03-05'
      

      在时间范围方面,time是精确到时分秒的datetime类型,如果不加转换,上面SQL会在日期后面自动补0,也就是查到的时间范围是2024-02-04 00:00:00~2024-03-05 00:00:00

      DATE(time) between '2024-02-04' and '2024-03-05'
      

      表示时间范围2024-02-04 00:00:00~2024-03-05 23:59:59

    2024年3月6日 星期三

    1. sql批量插入,重复更新
      说明:插入时,插入字段给id,但是values值id为null,也可以生成id,因为数据库主键设置自增的

          <insert id="insertWeatherForecastDataList">
              insert into wpp_weather_forecast_data(id, time, ten_speed, ten_direction, ten_direction_angle, thirty_speed,
              thirty_direction, thirty_direction_angle, fifty_speed, fifty_direction, fifty_direction_angle,seventy_speed,
              seventy_direction, seventy_direction_angle, hub_speed, hub_direction, hub_direction_angle, surface_temperature, surface_humidity, surface_pressure, create_time)
              values
              <foreach collection="list" item="item" index="index" separator=",">
                  (
                  <if test="item.id != null">
                      #{item.id},
                  </if>
                  #{item.time},
                  #{item.tenSpeed},
                  #{item.tenDirection},
                  #{item.tenDirectionAngle},
                  #{item.thirtySpeed},
                  #{item.thirtyDirection},
                  #{item.thirtyDirectionAngle},
                  #{item.fiftySpeed},
                  #{item.fiftyDirection},
                  #{item.fiftyDirectionAngle},
                  #{item.seventySpeed},
                  #{item.seventyDirection},
                  #{item.seventyDirectionAngle},
                  #{item.hubSpeed},
                  #{item.hubDirection},
                  #{item.hubDirectionAngle},
                  #{item.surfaceTemperature},
                  #{item.surfaceHumidity},
                  #{item.surfacePressure},
                  now()
                  )
              </foreach>
              ON DUPLICATE KEY UPDATE time = VALUES(time), ten_speed = VALUES(ten_speed), ten_direction =
              VALUES(ten_direction),
              ten_direction_angle = VALUES(ten_direction_angle), thirty_speed = VALUES(thirty_speed),thirty_direction=
              VALUES(thirty_direction),
              thirty_direction_angle = VALUES(thirty_direction_angle), fifty_speed = VALUES(fifty_speed),fifty_direction=
              VALUES(fifty_direction),
              fifty_direction_angle = VALUES(fifty_direction_angle), seventy_speed = VALUES(seventy_speed),
              seventy_direction = VALUES(seventy_direction), seventy_direction_angle = VALUES(seventy_direction_angle),
              hub_speed = VALUES(hub_speed), hub_direction = VALUES(hub_direction), hub_direction_angle
              = VALUES(hub_direction_angle), surface_temperature = VALUES(surface_temperature), surface_humidity =
              VALUES(surface_humidity), surface_pressure = VALUES(surface_pressure)
          </insert>
      
    2. 批量插入,如果集合isEmpty,sql会报错,应该提前判断一下集合是否非空

    3. 批量更新
      下面这样写,还是会执行n次sql

          <update id="updateBatch">
              <foreach collection="list" item="item" index="index" open="" close="" separator=";">
                  update wpp_weather_forecast_data
                  <set>
                      <trim suffixOverrides=",">
                          time = #{item.time},
                          ten_speed = #{item.tenSpeed},
                          ten_direction = #{item.tenDirection},
                          ten_direction_angle = #{item.tenDirectionAngle},
                          thirty_speed = #{item.thirtySpeed},
                          thirty_direction = #{item.thirtyDirection},
                          thirty_direction_angle = #{item.thirtyDirectionAngle},
                          fifty_speed = #{item.fiftySpeed},
                          fifty_direction = #{item.fiftyDirection},
                          fifty_direction_angle = #{item.fiftyDirectionAngle},
                          seventy_speed = #{item.seventySpeed},
                          seventy_direction = #{item.seventyDirection},
                          seventy_direction_angle = #{item.seventyDirectionAngle},
                          hub_direction = #{item.hubDirection},
                          hub_direction_angle = #{item.hubDirectionAngle},
                          surface_temperature = #{item.surfaceTemperature},
                          surface_humidity = #{item.surfaceHumidity},
                          surface_pressure = #{item.surfacePressure},
                          create_time = now()
                      </trim>
                  </set>
                  where id = ${item.id}
              </foreach>
          </update>
      

      mysql并没有提供直接的方法来实现批量更新,但是可以用点小技巧来实现。使用case when then

      SQL Server 负数显示零的,不用case when 啦_sql 负数 转换0-CSDN博客

      UPDATE mytable 
          SET myfield = CASE id 
              WHEN 1 THEN 'value'
              WHEN 2 THEN 'value'
              WHEN 3 THEN 'value'
          END
      WHERE id IN (1,2,3)
      
    4. 负数转换为0,类似于ifnull(speed,'-') as speed函数

              select device_id                                                                 as deviceId,
                     time                                                                      as time,
                     speed                                                                     as speed,
                     direction                                                                 as direction,
                     direction_angle                                                           as directionAngle,
                     case when 0 > actual_power_output + 0 then 0 else actual_power_output end as actualPowerOutput,
                     case when 0 > capacity_power + 0 then 0 else capacity_power end           as capacityPower,
                     case when 0 > available_power + 0 then 0 else available_power end         as availablePower,
                     case when 0 > ideal_power + 0 then 0 else ideal_power end                 as idealPower
              from wpp_wind_turbines_data as t
              where DATE_ADD(t.`time`, INTERVAL 11 DAY) >= #{time}
              // actual_power_output + 0在sql中,是将字符串转为数字,这里用的字段类型是varchar
      
    5. resultMap不添加映射,在实体类添加字段,但是查出来timeMonth别名和实体类字段对应,也会映射成功,说明自定义映射的作用其实是帮你取别名

end

  1. useGeneratedKeys你不知道的秘密
    【Mybatis】useGeneratedKeys参数用法及遇到的问题_java usegeneratedkeys-CSDN博客
  2. nginx反向代理
    Nginx详解(一文带你搞懂Nginx)-CSDN博客
    举例:我问访问google需要中间代理,我们访问的是Google的真实服务器地址,但是直接用国内的服务器无法访问国外的服务器,或者是访问很慢,需要在本地搭建一个服务器来帮助我们去访问。那这种就是正向代理。
    淘宝访问压力过大,往往部署多台服务器,因为服务器中间session不共享,那我们是不是在服务器之间访问需要频繁登录,那这个时候淘宝搭建一个过渡服务器,对我们是没有任何影响的,我们是登录一次,但是访问所有,这种情况就是 反向代理。此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器的地址
  3. cron表达式