SQL知识汇总

news/2025/2/13 22:03:20/

什么时候用存储过程合适

  1. 当一个事务涉及到多个SQL语句时或者涉及到对多个表的操作时就要考虑用存储过程;
  2. 当在一个事务的完成需要很复杂的商业逻辑时(比如,对多个数据的操作,对多个状态的判断更改等)要考虑;
  3. 还有就是比较复杂的统计和汇总也要考虑。

使用存储过程的优缺点

优点

  1. 速度快。尤其对于较为复杂的逻辑,减少了网络流量之间的消耗。
  2. 写程序简单,采用存储过程调用类,调用任何存储过程都只要1-2行代码。
  3. 升级、维护方便。
  4. 调试其实也并不麻烦,可以用查询分析器。

缺点

  1. 可移植性差。
  2. 数据量大时会比较耗时。

on duplicate key update实现(批量)插入或更新操作

一. 背景

  1. 背景:业务上经常有这样的需求场景,如果之前有这条数据,就做更新;如果没有,就做新增。
  2. 常用的处理方案:通过主键id或者其他唯一键判断DB中是否有这条数据,再判断调用insert或update语句。这样做逻辑处理起来比较复杂,降低代码效率,而且如果并发量高,可能会存在数据问题。

二. on duplicate key update概述

  1. 为了应对这种业务场景,MySQL有一种专有语法(insert into ... on duplicate key update),一条SQL语句实现插入或更新,可单条可批量。
  2. 使用要点
    1. 表要求必须有主键或唯一索引才能起效果,否则insert或update无效。
    2. 该语法是根据主键或唯一键来判断是新增还是更新。
    3. VALUES() 后面应为需要更新的字段,不需要更新的字段不用罗列。
    4. 遇到已存在记录(根据唯一键或主键)时,自动更新已有的数据;如果表中有多个唯一键(可以是单列索引或复合索引),则任意一个唯一键冲突时,都会自动更新数据。
    5. 所有操作均由SQL处理,不需要额外程序代码分析,能够大幅提高程序执行效率。

三. on duplicate key update的使用

创建测试—book科目表

CREATE TABLE `book` (`id` int NOT NULL AUTO_INCREMENT,`unique_code` varchar(30) NOT NULL,`book_name` varchar(20) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `unique_code` (`unique_code`) USING BTREE COMMENT 'book unique_code'
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

执行如下命令

insert into book(unique_code,book_name) values ('zs-001','中文') on duplicate key update book_name='数学';

成功插入一条数据
在这里插入图片描述

再次执行步骤一命令你会发现book_name值改变

在这里插入图片描述

Mysql中substring_index函数的使用

一、语法

substring_index(“待截取有用部分的字符串”,“截取数据依据的字符”,截取字符的位置N)

二、案例

SELECT SUBSTRING_INDEX(‘58.33.115.130’, ‘.’, 1); //结果是58

//以第一个句号为分割截取

SELECT SUBSTRING_INDEX(‘58.33.115.130’, ‘.’, -1); //结果是130

//从后面开始算第一个句号

SELECT SUBSTRING_INDEX(‘58.33.115.130’, ‘.’, 2); //结果是58.33

使用PREPARE 动态执行sql语句

PREPARE STMT FROM @sql_str; EXECUTE STMT; DEALLOCATE PREPARE STMT;

  1: DROP TABLE IF EXISTS alarm;2: CREATE TABLE alarm     ( id int(11) NOT NULL  auto_increment, name    varchar(255) default NULL,PRIMARY KEY (id));3:  4: INSERT INTO alarm (name) values('aa');5: INSERT INTO alarm (name) values('bb');6:  7: DROP procedure if exists statalarm;8: delimiter // 9: create procedure statalarm()10: begin11: SET @id  = '1,2';12: 13: SET @sql = 'select * from alarm where id IN (?)';14: PREPARE stmt FROM @sql;15: /* 16:  注意 EXECUTE 的最终语句是:select * from alarm where id IN ('1,2');17:  而不是 select * from alarm where id IN (1,2);18:  这是因为如果用户变量的值是字符串,在EXECUTE时 会自动的在变量的值前后加上引号19:  */20: EXECUTE stmt USING @id;21: DEALLOCATE PREPARE stmt;22: /*23:  如果想要 组成select * from alarm where id IN (1,2);可以使用下面的语句24: */25: SET @sql = concat('select * from alarm where id IN (',@id,')');26: PREPARE stmt FROM @sql;27: EXECUTE stmt ;28: DEALLOCATE PREPARE stmt;29:  30: end;//31: delimiter ;

DECLARE CONTINUE HANDLER FOR NOT FOUND 解释

在mysql的存储过程中经常会看到这句话:DECLARE CONTINUE HANDLER FOR NOT FOUND。

它的含义是:若没有数据返回,程序继续,并将变量IS_FOUND设为0 。

JAVA调用存储过程–MYSQL

业务代码

private void statFieldCollectByOpFlag(int opFlag){log.info("开始统计成果量opflag-{}", opFlag);try {Date date = new Date();String statDate = DateUtil.formatDate(date, "yyyy-MM-dd");// 如果当前时间在6点以前,则统计前一天的int hour = date.getHours();if (hour <= 6){statDate = DateUtil.formatDate(DateUtil.dateDiffer(date, "D",-1), "yyyy-MM-dd");}Map<String, Object> map = new HashMap<String, Object>();map.put("i_op_flag", opFlag);map.put("i_stat_date", statDate);map.put("o_err_no", 0);map.put("o_err_msg", "");try {statFruitRecordService.statFruitCountInfoByProc(map);BigDecimal bigDecimal= (BigDecimal)map.get("o_err_no");Integer errNo = bigDecimal.intValue();if ( !errNo.equals(0) ){String errMsg = (String)map.get("o_err_msg");log.error("统计成果量["+opFlag+"]异常["+errNo.toString()+"]"+errMsg);}} catch (Exception e) {log.error("统计成果量["+opFlag+"]异常", e);}log.info("结束统计成果量opflag-{}", opFlag);} catch (Exception e) {log.error("结束统计成果量["+opFlag+"]异常", e);}}

Mapper代码

<select id="statFruitCountInfo" parameterType="map" statementType="CALLABLE">{call sp_stat_fruit_record(#{map.i_op_flag			, mode=IN	, jdbcType=NUMERIC},#{map.i_stat_date	    , mode=IN	, jdbcType=VARCHAR},#{map.o_err_no		    , mode=OUT	, jdbcType=NUMERIC},#{map.o_err_msg		    , mode=OUT	, jdbcType=VARCHAR})}</select>

存储过程

-- --------------------------------------------------------
-- 主机:                           xx.xx.xx.xx
-- 服务器版本:                     5.7.27-log - MySQL Community Server (GPL)
-- 服务器操作系统:                 linux-glibc2.12
-- HeidiSQL 版本:                 11.0.0.5919
-- --------------------------------------------------------/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;-- 导出  存储过程 sp_stat_fruit_record 结构
DROP PROCEDURE IF EXISTS `sp_stat_fruit_record`;
DELIMITER //
CREATE PROCEDURE `sp_stat_fruit_record`(IN `i_op_flag` int,IN `i_stat_date` varchar(10),OUT `o_err_no` int,OUT `o_err_msg` varchar(128)
)COMMENT '[104]入库成果量统计'
update_proc:
BEGIN#Routine body goes here...DECLARE sp_code INT DEFAULT 104;-- START Declare ConditionsDECLARE sql_err_code INT DEFAULT 0;DECLARE var_cnt INT DEFAULT 0;DECLARE var_size INT DEFAULT 0;DECLARE var_idx INT DEFAULT 0;DECLARE var_tail varchar(16) DEFAULT '';DECLARE CONTINUE HANDLER FOR 1054 SET sql_err_code = 1054;-- Unknown columnDECLARE CONTINUE HANDLER FOR 1062 SET sql_err_code = 1062;-- Duplicate entryDECLARE CONTINUE HANDLER FOR 1329 SET sql_err_code = 0;-- No data - zero rows fetched, SELECT ed, or processedDECLARE CONTINUE HANDLER FOR SQLEXCEPTION set sql_err_code = 9999;-- END Declare Conditions-- check param-- START TRANSACTION;IF 1 != 1OR (i_op_flag IS NULL AND '' <> (@param_name := 'i_op_flag'))OR (i_stat_date IS NULL AND '' <> (@param_name := 'i_stat_date'))THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 1);SET o_err_msg = CONCAT('缺少参数', @param_name);LEAVE update_proc;END IF;SET @element_codes = 'aoi,building,aoi_gate,unit,poi,room,internal_road';-- i_stat_date格式 'YYYY-MM-DD'-- i_op_flag-- 1 转历史-- 2 截止昨日总量统计-- 3 当日审核入库统计-- 4 当日采集入库统计IF 1 = i_op_flag THEN# 保留1周的数据SET @del_date = CONCAT(DATE_SUB(i_stat_date, INTERVAL 1 WEEK), ' 00:00:00');SET var_size = LENGTH(@element_codes)- LENGTH(REPLACE(@element_codes,",","")) + 1;SET var_idx=1;WHILE var_idx <= var_size  DOSET var_tail = SUBSTRING_INDEX(SUBSTRING_INDEX(@element_codes,',',var_idx),',',-1);SET @sql_str = CONCAT('INSERT INTO his_fruit_', var_tail, ' SELECT * FROM t_fruit_', var_tail, ' WHERE update_time < "', @del_date, '"');PREPARE STMT FROM @sql_str;EXECUTE STMT;DEALLOCATE PREPARE STMT;IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 11);SET o_err_msg = CONCAT('审核成果转历史失败', var_tail);LEAVE update_proc;LEAVE update_proc;END IF;SET @sql_str = CONCAT('DELETE FROM t_fruit_', var_tail, ' WHERE update_time < "', @del_date, '"');PREPARE STMT FROM @sql_str;EXECUTE STMT;DEALLOCATE PREPARE STMT;IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 12);SET o_err_msg = CONCAT('审核成果转历史失败', var_tail);LEAVE update_proc;END IF;SET @sql_str = CONCAT('INSERT INTO his_collect_fruit_', var_tail, ' SELECT * FROM t_collect_fruit_', var_tail, ' WHERE update_time < "', @del_date, '"');PREPARE STMT FROM @sql_str;EXECUTE STMT;DEALLOCATE PREPARE STMT;IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 13);SET o_err_msg = CONCAT('采集成果转历史失败', var_tail);LEAVE update_proc;END IF;SET @sql_str = CONCAT('DELETE FROM t_collect_fruit_', var_tail, ' WHERE update_time < "', @del_date, '"');PREPARE STMT FROM @sql_str;EXECUTE STMT;DEALLOCATE PREPARE STMT;IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 14);SET o_err_msg = CONCAT('采集成果转历史失败', var_tail);LEAVE update_proc;END IF;SET var_idx = var_idx + 1;END WHILE;-- 2 截止昨日总量统计ELSEIF 2 = i_op_flag THENSELECT COUNT(1) INTO var_cnt FROM t_stat_fruit_record WHERE stat_date = '0000-00-00';IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 21);SET o_err_msg = '截止昨日总量统计失败';LEAVE update_proc;END IF;IF var_cnt = 0 THENINSERT INTO t_stat_fruit_recordSELECT '0000-00-00', sum(right_count), sum(error_count), 'ALL',sum(right_count), sum(error_count), now(), now() FROM t_stat_fruit_record;INSERT INTO t_stat_fruit_recordSELECT '0000-00-00', sum(right_count), sum(error_count), element_code, sum(right_count), sum(error_count), now(), now() FROM t_stat_fruit_recordWHERE element_code != 'ALL'GROUP BY element_code;ELSEUPDATE t_stat_fruit_record SET plan_project_id = right_count, plan_collect_id = error_count, update_time = now() WHERE stat_date = '0000-00-00';END IF;IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 22);SET o_err_msg = '截止昨日总量统计失败';LEAVE update_proc;END IF;-- 3 当日审核入库统计ELSEIF 3 = i_op_flag THENSET @start_date = CONCAT(i_stat_date, ' 00:00:00');SET @end_date = CONCAT(i_stat_date, ' 23:59:59');SET var_size = LENGTH(@element_codes)- LENGTH(REPLACE(@element_codes,",","")) + 1;SET var_idx=1;WHILE var_idx <= var_size  DOSET var_tail = SUBSTRING_INDEX(SUBSTRING_INDEX(@element_codes,',',var_idx),',',-1);SET @sql_str = CONCAT(' INSERT INTO t_stat_fruit_recordSELECT "',i_stat_date,'", project_manage_id, plan_id, "', var_tail,'", SUM(CASE WHEN result_type = 1 THEN 1 ELSE 0 END) AS  rightCount, SUM(CASE WHEN result_type = 2 THEN 1 ELSE 0 END) AS  errorCount, now(), now()FROM t_fruit_', var_tail, ' WHERE update_time >= "', @start_date, '" AND update_time <= "', @end_date, '"GROUP BY project_manage_id, plan_idON DUPLICATE KEY UPDATEright_count = VALUES(right_count), error_count = VALUES(error_count), update_time = VALUES(update_time) ');-- SELECT @sql_str;PREPARE STMT FROM @sql_str;EXECUTE STMT;DEALLOCATE PREPARE STMT;IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 31);SET o_err_msg = CONCAT('截止昨日总量统计失败', var_tail);LEAVE update_proc;END IF;SET var_idx = var_idx + 1;END WHILE;-- 4 当日采集入库统计ELSEIF 4 = i_op_flag THENSET @start_date = CONCAT(i_stat_date, ' 00:00:00');SET @end_date = CONCAT(i_stat_date, ' 23:59:59');SET var_size = LENGTH(@element_codes)- LENGTH(REPLACE(@element_codes,",","")) + 1;SET var_idx=1;WHILE var_idx <= var_size  DOSET var_tail = SUBSTRING_INDEX(SUBSTRING_INDEX(@element_codes,',',var_idx),',',-1);SET @sql_str = CONCAT(' INSERT INTO t_stat_fruit_collectSELECT "',i_stat_date,'", project_manage_id, plan_id, "', var_tail,'", SUM(CASE WHEN result_type = 1 THEN 1 ELSE 0 END) AS  rightCount, SUM(CASE WHEN result_type = 2 THEN 1 ELSE 0 END) AS  errorCount, now(), now()FROM t_collect_fruit_', var_tail, ' WHERE update_time >= "', @start_date, '" AND update_time <= "', @end_date, '"GROUP BY project_manage_id, plan_idON DUPLICATE KEY UPDATEright_count = VALUES(right_count), error_count = VALUES(error_count), update_time = VALUES(update_time) ');-- SELECT @sql_str;PREPARE STMT FROM @sql_str;EXECUTE STMT;DEALLOCATE PREPARE STMT;IF 0 <> sql_err_code THENSET o_err_no = fn_sp_errno(sql_err_code, sp_code, 41);SET o_err_msg = CONCAT('截止昨日总量统计失败', var_tail);LEAVE update_proc;END IF;SET var_idx = var_idx + 1;END WHILE;ELSESET o_err_no = fn_sp_errno(sql_err_code, sp_code, 99);SET o_err_msg = '未支持的操作类型';LEAVE update_proc;END IF;-- ok-- COMMIT;-- okSET o_err_no = 0;
END//
DELIMITER ;/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

存储过程实例


DELIMITER $$
USE `oso_isp` $$ 
DROP PROCEDURE IF EXISTS `proc_wh_stock_in_order_ok` $$ 
CREATE PROCEDURE `proc_wh_stock_in_order_ok` ( IN in_id INT , IN in_user_id INT) 
BEGIN
DECLARE var_id INT DEFAULT 0;
DECLARE var_asset_type INT DEFAULT 1;
DECLARE var_quantity INT DEFAULT 0;
DECLARE var_item_id INT DEFAULT 0;
DECLARE var_i INT DEFAULT 0;
DECLARE var_asset_id INT DEFAULT 0;
DECLARE done INT DEFAULT - 1;
/* 声明游标 */
DECLARE myCursor CURSOR FOR SELECTa.id,b.asset_type,a.quantity 
FROMwh_stock_in_order_item aINNER JOIN wh_asset b ON a.asset_id = b.id 
WHEREa.stock_in_order_id = in_id;
/* 当游标到达尾部时,mysql自动设置done=1 */
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
/* 打开游标 */
OPEN myCursor;
/* 循环开始 */
myLoop :
LOOP
/* 移动游标并赋值 */FETCH myCursor INTO var_id,var_asset_type,var_quantity;
IF done = 1 THENUPDATE wh_stock_in_order SET STATUS = 1 WHEREid = in_id;LEAVE myLoop;
END IF;SELECT id INTO var_asset_id 
FROMwh_asset_inventory 
WHEREstock_in_order_item_id = var_id;IF var_asset_id > 0 THENUPDATE wh_asset_inventory sJOIN (SELECTa.id,a.asset_id,c.CODE,0 ,b.supplier_id,b.buyer_id,a.manufacture_date,b.purchase_date,DATE_ADD( a.manufacture_date, INTERVAL c.warranty_period MONTH ) AS end_repair_date,0,b.storekeeper_id,b.Stockroom_id,a.quantity,0,a.id,a.unit_price,0,in_user_id			FROMwh_stock_in_order_item aINNER JOIN wh_stock_in_order b ON a.stock_in_order_id = b.idINNER JOIN wh_asset c ON c.id = a.asset_id WHEREa.id = var_id AND a.is_deleted = 0) k ON k.id = s.stock_in_order_item_id SET s.`asset_id` = k.asset_id,s.`code` = k.CODE,s.`location_id` = 0,s.`supplier_id` = k.supplier_id,s.`buyer_id` = k.buyer_id,s.`manufacture_date` = k.manufacture_date,s.`purchase_date` = k.purchase_date,s.`end_repair_date` = k.end_repair_date,s.`keeping_department_id` = 0,s.`storekeeper_id` = k.storekeeper_id,s.`Stockroom_id` = k.Stockroom_id,s.`quantity` = k.quantity,s.`status` = 0,s.`stock_in_order_item_id` = k.id,s.unit_price = k.unit_price,s.`is_deleted` = 0 ,s.`created_by` = in_user_id	WHEREs.is_deleted = 0;ITERATE myLoop;
END IF;INSERT INTO `wh_asset_inventory` (`asset_id`,`code`,`location_id`,`supplier_id`,`buyer_id`,`manufacture_date`,`purchase_date`,`end_repair_date`,`keeping_department_id`,`storekeeper_id`,`Stockroom_id`,`quantity`,`status`,`stock_in_order_item_id`,unit_price,`is_deleted`,`created_by`
) SELECT
a.asset_id,
c.CODE ,
0,
b.supplier_id,
b.buyer_id,
a.manufacture_date,
b.purchase_date,
DATE_ADD( a.manufacture_date, INTERVAL c.warranty_period MONTH ),
0,
b.storekeeper_id,
b.Stockroom_id,
a.quantity,
0 ,
a.id ,
a.unit_price,
0 ,
in_user_id
FROMwh_stock_in_order_item aINNER JOIN wh_stock_in_order b ON a.stock_in_order_id = b.idINNER JOIN wh_asset c ON c.id = a.asset_id 
WHEREa.id = var_id AND a.is_deleted = 0;SELECT LAST_INSERT_ID( ) INTO var_item_id;UPDATE wh_asset a
JOIN ( SELECT asset_id, unit_price FROM wh_stock_in_order_item WHERE id = var_id ) b ON b.asset_id = a.id 
SET a.reference_price = b.unit_price;
UPDATE wh_asset_inventory 
SET CODE = CONCAT( CODE, '-', id ) 
WHEREid = var_item_id;/* 循环结束 */
END LOOP myLoop;
/* 关闭游标 */
CLOSE myCursor;END $$
DELIMITER ;

http://www.ppmy.cn/news/62028.html

相关文章

Nmap安全工具使用手册

Nmap安全工具使用手册 什么是Nmap&#xff1f; Nmap(Network Mapper) 是一款自由、开源的网络探测和安全审核工具。它适用于Windows、Linux、UNIX等各种操作系统平台&#xff0c;可以帮助管理员或安全研究人员判断目标机器的状态和对应的服务程序&#xff0c;同时也能探查隐蔽…

【Linux网络】网络应用层的 http 和 https协议

文章目录 1、http协议1.1 认识URL1.2 http协议格式1.3 http的方法&#xff08;GET和POST&#xff09;1.4 状态码1.5 cookie1.6 短连接和长连接 2、https协议2.1 常见的加密方式2.2 探究https协议的加密2.3 CA证书 1、http协议 在之前学习序列化和反序列化的时候&#xff0c;认…

模糊PID模糊控制(清晰化方法梯形图实现)

模糊PID的模糊化请参看下面的博客文章: 博途PLC模糊PID三角隶属度函数指令(含Matlab仿真)_plc 模糊pid_RXXW_Dor的博客-CSDN博客三角隶属度函数FC,我们采用兼容C99标准的函数返回值写法,在FB里调用会更加直观,下面给大家具体讲解代码。常规写法的隶属度函数FC可以参看下…

人大金仓亮相国际金融展,打造“金融+产业+生态”创新模式

4月27日&#xff0c;以“荟萃金融科技成果&#xff0c;展现数字金融力量&#xff0c;谱写金融服务中国式现代化新篇章”为主题的2023中国国际金融展圆满落幕。作为已经举办30年的行业盛会&#xff0c;人大金仓再一次重磅亮相&#xff0c;全方位展示国产数据库前沿应用和创新服务…

php 设置meta标签中的keywords | description | content-type | copyright的方法函数

怎么设置meta标签中的所有值 if(!function_exists(meta)) {/** 从键/值数组生成元标记&#xff0c;生成meta标签的keywords,description,Content-type,author等* param array* param string* param string* param string* return string*/function meta($name,$conte…

pdf怎么分割成一页一页的文件?

pdf怎么分割成一页一页的文件&#xff1f;相信很多使用电脑办公的小伙伴们都知道&#xff0c;无论是pdf文件还是ppt文件其都是由很多页组成的。一般当我们需要其中的部分内容时候&#xff0c;可以通过手动的方式将ppt文件来拆分成一页一页的来使用。但是这种手动的方法对于pdf文…

22. 资源的调度——污点与容忍(Taints 和 Tolerations)

本章讲解知识点 前言Taints 和 Tolerations 概念实验污点的常用场景1. 前言 假如我们有一种场景,我们希望某些节点是专用节点,比如 Kubernetes master 节点,就不希望业务应用 Pod 调度过来。或者防止一些非关键的 Pod 占用一些特殊的节点资源,如大页内存。这个时候我们就希…

【软件测试】| 软件测试 - 答疑篇

&#x1f397;️ 主页&#xff1a;小夜时雨 &#x1f397;️ 专栏&#xff1a;软件测试 &#x1f397;️ 如何优雅的活着&#xff0c;是我找寻的方向 目录 一、什么是软件测试二、测试和调试的区别三、软件测试和开发的区别 一、什么是软件测试 最常见的理解是&#xff1a;软…