SQL 使用小记
SQL语法范式:
SELECT xxx FROM xxx JOIN xxx WHERE xxx GROUP BY xxx HAVING xxx ORDER BY xx LIMIT xxx FOR xxx
几个概念:statement、clause、expression
statement:语句,一条SQL
clause:子句,一条SQL里的部分,比如上面的SELECT xxx,JOIN xxx,HAVING xxx等都是。一条statement由若干条clause按一定顺序组成(语法规则)。
experession:表达式,clause中的一部分,比如 WHERE id>1 里的 id>1 是个expression
一些不常见的SQL语句:
不常见的一些SQL: SELECT COALESCE(age,'42') FROM users; SELECT * FROM `users` FOR UPDATE NOWAIT; SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`; SELECT * FROM `users` USE INDEX (`idx_user_name`); SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"; UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING *; UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING `name`, `salary`; DELETE FROM `users` WHERE role = "admin" RETURNING *; DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary`; INSERT IGNORE INTO users (id, name) VALUES (1, 'Alice'); create table new_transactions like transactions;
create table temp_active_account engine=MyISAM as select * from active_account; -- 会将后者的表结构及数据复制过来,只不过改变了Engine。如果没指定engine则是完全原样复制了
INSERT INTO `withholding_tax` (`nn_uid`,`account_id`,`market`,`exchange`,`symbol`,`clearing_date`,`settlement_date`,`amount`,`ccy`,`related_type`,`tax_type`,`extra`,`id`) VALUES (0,11061163,6,'US','AAPL',20240807,20240807,'-3.3','USD',1,3,'{\"EntryType\": \"DIVROC\"}',152) ON DUPLICATE KEY UPDATE `nn_uid`=VALUES(`nn_uid`),`account_id`=VALUES(`account_id`),`market`=VALUES(`market`),`exchange`=VALUES(`exchange`),`symbol`=VALUES(`symbol`),`clearing_date`=VALUES(`clearing_date`),`settlement_date`=VALUES(`settlement_date`),`amount`=VALUES(`amount`),`ccy`=VALUES(`ccy`),`related_id`=VALUES(`related_id`),`related_type`=VALUES(`related_type`),`tax_type`=VALUES(`tax_type`),`extra`=VALUES(`extra`)"
0、SQL基础Note
where:行过滤
group by:分组
having:分组过滤
exists:相关子查询
where和having:针对行过滤 与 针对分组结果过滤的区别
where子查询和exists子查询:where包含子查询时先查得where后的子查询的结果再根据结果进行where前的主查询;exists先执行exists前的主查询得到结果再对每个结果进行子查询,可见后者在数据量大时会很耗时,慎用。
视图:视图通常用于查询而很少用于更新,并非所有的视图都可以进行更新操作,如视图中存在分组(group by)、联结、子查询、并(unoin)、聚合函数(sum/count等)、计算字段、DISTINCT等都不能对视图进行更新操作。
索引:单列索引、复合索引、唯一索引、主键索引 聚簇索引 等。
索引的设计:
where子句中的列可能最适合做为索引
不要尝试为性别或者有无这类字段等建立索引(因为类似性别的列,一般只含有“0”和“1”,无论搜索结果如何都会大约得出一半的数据)
如果创建复合索引,要遵守最左前缀法则。即查询从索引的最左前列开始,并且不跳过索引中的列
不要过度使用索引。每一次的更新,删除,插入都会维护该表的索引,更多的索引意味着占用更多的空间
使用InnoDB存储引擎时,记录(行)默认会按照一定的顺序存储,如果已定义主键,则按照主键顺序存储,由于普通索引都会保存主键的键值,因此主键应尽可能的选择较短的数据类型,以便节省存储空间
不要尝试在索引列上使用函数。
聚簇索引(clustered index)和非聚簇索引(nonclustered index):
区别:聚簇索引中index key和data一起存储在叶节点,因此索引项顺序和数据顺序一样;非聚簇索引中数据不在叶节点而是单独存储,叶节点存储index key和指向数据的指针,故两者顺序不一样。可见,聚簇索引和非聚簇索引分别相当于数据的一级索引和二级索引。
一张表最多只能有一个聚簇索引,因数据顺序只会有一种。
例子:按拼音查字典和按部首查字典分别是聚簇索引和非聚簇索引,前者拼音列表的顺序与字的组织顺序一样,后者部首的顺序与字的顺序不一样。
使用场景:由于聚簇索引索引项与数据的顺序一致,因此索引项改时要相应地调整数据位置,因此聚簇索引适合的场景有 更新少、范围查询多、数据有序(如自增id)、不同值不是很多等。如果数据无序,则插入新数据时索引项可能插入到中间从而导致需要移动已存储的数据保持一致顺序。
MySQL InnoDB存储引擎默认对主键用聚簇索引。
字符串类型字段不适合用聚簇索引,因为字符串值随机,插入后通常需要调整已由数据的位置。
使用场景的区别:
MySQL中若使用了聚簇索引,则普通列的索引项的value为primary key;若未使用聚簇索引,则上述value为指向数据的地址,因此存在聚簇索引时对于普通列查询比不存在聚簇索引时需要更多IO。示例图如下:
(1) 、(2)
更多可参阅:https://www.cnblogs.com/z-sm/p/6005901.html
为什么索引字段要尽可能小(如用int比字符串好)?原因:
数据量固定时要使树高尽可能小 -> 每个节点中的索引项应尽可能多 ;而数据库中索引树(如B+树)节点占用空间大小(是OS页大小)是固定的,此时要使得索引项尽可能多,索引项的大小应该尽可能小。
另外,小的索引字段在磁盘、内存、CPU中占用空间更小,更容易处理
子查询与join查询:一般来说前者效率通常比后者低。
假设 用户订单表user_order有10w条数据,商品表goods有1000条数据,其间通过商品id关联,且g.type=1的goods有100条数据。前者子查询后需要100个子查询数据每个用户10w数据集进行查询、后者则不需要。
select uo.* from user_order uo where uo.gid in ( select g.id from goods g where g.type=1 ); select uo.* from user_order uo inner join goods g on uo.gid=g.id and g.type=1;
逗号分隔多表的多表查询其实就是inner join查询:from a, b where a.id=b.aid 与 from a inner join b on a.id=b.aid 等价
count(1)、count(*):不会忽略值为null的字段
count(列名):会忽略值为null的字段
20190825
联合主键(Composite Primary Key)的使用场景有两个:
- 从属关系的子表。如一个Experiment有多个ExperimentStep,则ExperimentStep中可以用experimentId、stepNum作为联合主键;
- 多对多关系的关联表:如Student、Course间的关系是多对多的,则其关联表Student_Course可以用studentId、courseId作为联合主键。
实际经验总结:如果业务中要求有软删除,则上述两种场景都不能设置联合主键不然可能会报主键冲突。相反,应该都额外加个id字段作为主键,并去掉联合主键。
修改表的字符集:
alter table course_export convert to character set utf8mb4 collate utf8mb4_unicode_ci;
1、 针对返回结果的case语句
示例
select id, name, case user_role when 0 then "管理员" when 1 then "未注册用户" when 2 then "注册用户" else concat("未知值:", user_role) end
“角色” from user
select、update语句中都可使用case when语句。select 与 case结合使用最大的好处有两点,一是在显示查询结果时可以灵活的组织格式,二是有效避免了多次对同一个表或几个表的访问。
case when 的使用较全面的总结强烈推荐参阅这篇文章。
2、动态添加where条件
SELECT id, name, school_number AS schoolNumber, gender, grade FROM student where (?4 is NULL or name like %?4% or school_number like %?4%) and (?5 is NULL or gender= ?5) and (?6 is NULL or grade like %?6%)
该句应用背景为支持用户搜索返回模糊匹配结果,特点在于用户可以动态选择搜索条件的个数,?4、?5、?6 三个参数用户传不传均可。即不用因为用户传送参数个数的不同进行写多条SQL语句或在业务层面进行if判断,而只用一条SQL。
也可以使用if语句,示例:
update course c set c.is_enable= if (c.license_expire_time > now() , true, false), c.disable_time= if (c.is_enable =true, null, now());
3、SQL事件
可以让事件只发生一次或多次或周期性发生。
详见:https://www.yiibai.com/mysql/working-mysql-scheduled-event.html
示例:
-- '以下为course表中is_enable, disable_time自动更新的事件'; SET GLOBAL event_scheduler = ON; drop event if exists `autoDisableCourse`; create event `autoDisableCourse` on schedule every 1 minute DO update course c set c.is_enable= if (c.license_expire_time > now() , true, false), c.disable_time= if (c.is_enable =true, null, now());
4、SQL触发器
示例:
-- '以下为course表中exp_num字段自动更新有关的两个触发器'; drop trigger if exists `updateExpNumInCourseTableAfterInsertExp` ; DELIMITER ;; CREATE trigger `updateExpNumInCourseTableAfterInsertExp` after insert on experiment for each row begin -- '插入一个实验时更新课程表中的实验数字段'; update course c set c.exp_num =(select count(e.id) from experiment e where e.course_id=NEW.course_id) where c.id = NEW.course_id; end ;; DELIMITER ; drop trigger if exists `updateExpNumInCourseTableAfterDeleteExp` ; DELIMITER ;; CREATE trigger `updateExpNumInCourseTableAfterDeleteExp` after delete on experiment for each row begin -- '删除一个实验时更新课程表中的实验数字段'; update course c set c.exp_num =(select count(e.id) from experiment e where e.course_id=OLD.course_id) where c.id = OLD.course_id; end ;; DELIMITER ;
5、关联表组织和命名
一对一、一对多关系通过外键来关联表,外键存储在主动关联的表即reference table(而非被管理表即referenced table)中;命名采用user、user_role
多对多关系通过主键来关联表,将reference table和referenced table的主键单独存在第三张表中来维护关联关系;命名采用user、region、user_region
可以看到,两者都会有下划线,此时不好快速区分到底 带下划线的表是 一张详细表(如user_role)还是一张 用来表示两张表关联关系的表(如user_region)。解决方法:一种是前者去掉 "user_" 前缀;另一种是后者统一加上 可区分的前缀如 "re_user_region" 。
7、字符串当做数字比较
如对于chapter="2"、"12",在sql中order by chapter asc得到的结果是字典序升序排序结果 "12"、"2"。若想使之与被当成数字时的升序排序结果一样,可以用语句order by chapter+0,得到的是"12"、"2"。
在native sql和JPQL中都可用此法。
可参阅:https://stackoverflow.com/questions/16242769/mysql-order-by-0-then-largest
8、查询时返回字段值的初始化
通过sql的 ifnull 函数,示例: select ifnull(t.courseNum, 0) as courseNun from course;
更好的方法是使用 COALESCE 函数,COALESCE(expression_1, expression_2, ...,expression_n)依次参考各参数表达式,遇到非null值即停止并返回该值。如果所有的表达式都是空值,最终将返回一个空值。
distinct 和 group by
distinct:用于去重,返回唯一的记录。
group by:用于分组、聚合,返回分组后的记录。
distinct 和 group by 一般不一起用。去重的话,group by也可以用于去重(此时没有聚合),但是distinct更快。
select distinct accountId from account; select accountId from account group by accountId;