SQL Tips汇集及常见问题
表与数据
-- 创建并初始化部门表
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (
`deptno` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '部门编号',
`dname` varchar(15) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '部门名称',
`loc` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '部门所在位置',
PRIMARY KEY (`deptno`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of dept
-- ----------------------------
INSERT INTO `dept` VALUES (1, '张三', '{\"dname\": \"销售部\", \"dno\": \"1\", \"dmanagerId\": \"3\"}');
INSERT INTO `dept` VALUES (2, '李四', '{\"dname\": \"财务部\", \"deptno\": \"2\", \"dmanagerId\": \"4\"}');
INSERT INTO `dept` VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO `dept` VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO `dept` VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO `dept` VALUES (40, 'OPERATIONS', 'BOSTON');
-- 创建并初始化员工表
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp` (
`empno` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '雇员编号',
`ename` varchar(15) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '雇员姓名',
`job` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '雇员职位',
`mgr` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '雇员对应的领导的编号',
`hiredate` date NULL DEFAULT NULL COMMENT '雇员的雇佣日期',
`sal` decimal(7, 2) NULL DEFAULT NULL COMMENT '雇员的基本工资',
`comm` decimal(7, 2) NULL DEFAULT NULL COMMENT '奖金',
`deptno` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '所在部门',
PRIMARY KEY (`empno`) USING BTREE,
INDEX `deptno`(`deptno`) USING BTREE,
CONSTRAINT `emp_ibfk_1` FOREIGN KEY (`deptno`) REFERENCES `dept` (`deptno`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 7935 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '雇员表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800.00, NULL, 20);
INSERT INTO `emp` VALUES (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600.00, 300.00, 30);
INSERT INTO `emp` VALUES (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250.00, 500.00, 30);
INSERT INTO `emp` VALUES (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975.00, NULL, 20);
INSERT INTO `emp` VALUES (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250.00, 1400.00, 30);
INSERT INTO `emp` VALUES (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850.00, NULL, 30);
INSERT INTO `emp` VALUES (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450.00, NULL, 10);
INSERT INTO `emp` VALUES (7788, 'SCOTT', 'ANALYST', 7566, '1987-07-13', 3000.00, NULL, 20);
INSERT INTO `emp` VALUES (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5000.00, NULL, 10);
INSERT INTO `emp` VALUES (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500.00, 0.00, 30);
INSERT INTO `emp` VALUES (7876, 'ADAMS', 'CLERK', 7788, '1987-07-13', 1100.00, NULL, 20);
INSERT INTO `emp` VALUES (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950.00, NULL, 30);
INSERT INTO `emp` VALUES (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000.00, NULL, 20);
INSERT INTO `emp` VALUES (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300.00, NULL, 10);
LEFT JOIN与WHERE及On Clause
SELECT A.* ,B.*
FROM dept A
LEFT JOIN emp B
ON A.deptno = B.deptno AND mgr = 7839
-- 等同于如下写法:
SELECT A.* ,B.*
FROM dept A
LEFT JOIN
(SELECT *
FROM emp B WHERE mgr = 7839
)B
ON A.deptno = B.deptno
即先通过B表(emp)按照mgr=7839筛选出一批deptno,然后再以A表(dept)为主表通过dept进行关联出最终结果。
deptno | dname | loc | empno | ename | job | mgr | hiredate | sal | comm | deptno(1) |
1 | 张三 | {"dname": "销售部", "dno": "1", "dmanagerId": "3"} | ||||||||
2 | 李四 | {"dname": "财务部", "deptno": "2", "dmanagerId": "4"} | ||||||||
10 | ACCOUNTING | NEW YORK | ||||||||
20 | RESEARCH | DALLAS | ||||||||
30 | SALES | CHICAGO | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 |
30 | SALES | CHICAGO | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
30 | SALES | CHICAGO | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
30 | SALES | CHICAGO | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 |
30 | SALES | CHICAGO | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | 30 | |
40 | OPERATIONS | BOSTON |
而如果是想仅取A和B表关联后mgr=7839的记录,则需要在WHERE Clause后写条件,及如下形式:
SELECT A.* ,B.*
FROM dept A
LEFT JOIN emp B
ON A.deptno = B.deptno
WHERE mgr = 7698
deptno | dname | loc | empno | ename | job | mgr | hiredate | sal | comm | deptno(1) |
30 | SALES | CHICAGO | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 |
30 | SALES | CHICAGO | 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
30 | SALES | CHICAGO | 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
30 | SALES | CHICAGO | 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 |
30 | SALES | CHICAGO | 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | 30 |
注:在inner join(一般缩写为join)时在ON clause后写条件和where后结果是一致的。
SELECT A.* ,B.*
FROM dept A
INNER JOIN emp B
ON A.deptno = B.deptno
WHERE mgr = 7839
-- 结果等同于:
SELECT A.* ,B.*
FROM dept A
INNER JOIN emp B
ON A.deptno = B.deptno AND mgr = 7839
低版本mysql实现row_number
低版本mysql(如5.7)系统没有内置的窗口分析函数,比如ROW_NUMBER,需要手动实现。
select empno,deptno,sal,rank from (
select heyf_tmp.empno,heyf_tmp.deptno,heyf_tmp.sal,@rownum := @rownum+1 ,
if(@pdept= heyf_tmp.deptno,@rank:=@rank +1, @rank:= 1) as rank,
@pdept:=heyf_tmp.deptno
from (
select empno,deptno,sal from emp order by deptno asc ,sal desc
) heyf_tmp ,
(select @rownum:=0 , @pdept:= null ,@rank:= 0) a
) result
order by deptno,rank;
empno | deptno | sal | rank |
7839 | 10 | 5000.00 | 1 |
7782 | 10 | 2450.00 | 2 |
7934 | 10 | 1300.00 | 3 |
7788 | 20 | 3000.00 | 1 |
7902 | 20 | 3000.00 | 2 |
7566 | 20 | 2975.00 | 3 |
7876 | 20 | 1100.00 | 4 |
7369 | 20 | 800.00 | 5 |
7698 | 30 | 2850.00 | 1 |
7499 | 30 | 1600.00 | 2 |
7844 | 30 | 1500.00 | 3 |
7521 | 30 | 1250.00 | 4 |
7654 | 30 | 1250.00 | 5 |
7900 | 30 | 950.00 | 6 |
CASE WHEN分区间统计
SELECT CASE WHEN sal > 0 AND sal <= 2000 THEN 'level1'
WHEN sal > 2000 AND sal <= 4000 THEN 'level2'
ELSE 'leve13' END level_name,COUNT(DISTINCT empno) emp_num,SUM(sal) sum_sal
FROM emp
GROUP BY
CASE WHEN sal > 0 AND sal <= 2000 THEN 'level1'
WHEN sal > 2000 AND sal <= 4000 THEN 'level2'
ELSE 'leve13' END
ORDER BY level_name
level_name | emp_num | sum_sal |
leve13 | 1 | 5000.00 |
level1 | 8 | 9750.00 |
level2 | 5 | 14275.00 |
NULL值不参与聚合函数的计算
-- 对comm字段求平均值
SELECT AVG(comm),(300+500+1400+0)/4
FROM emp
-- 这里不难发现,comm为NULL的不参与AVG聚合计算,即仅有值的参与计算,具体为:
-- (300+500+1400+0)/4
-- 写法等价于如下:
SELECT SUM(comm)/COUNT(1) avg_value
FROM emp WHERE comm IS NOT NULL
NULL值与GREATEST、LEAST的使用
-- 计算所有部门累计工资里的最大值,注意没有部门40。
SELECT
GREATEST(
SUM( CASE WHEN deptno = 10 THEN sal ELSE NULL END) ,
SUM( CASE WHEN deptno = 20 THEN sal ELSE NULL END) ,
SUM( CASE WHEN deptno = 30 THEN sal ELSE NULL END),
SUM( CASE WHEN deptno = 40 THEN sal ELSE NULL END)
) max_value
FROM emp
max_value |
查不到的数据返回默认值
-- 查找员工号为10000的工资,如果查不到则返回为0。
SELECT IFNULL(MAX(sal),0)
FROM emp
WHERE empno = 10000
sal |
0.00 |
统计连续字段的个数
-- 统计连续月份的最大值,注意给出的月份数据必须持续递增的且不能重复。
SELECT MAX(cnt) max_val
FROM
(
SELECT datediff,COUNT(1) cnt
FROM
(
SELECT seq,order_date,DATE_ADD(order_date,interval -seq month) datediff FROM
(
SELECT 1 seq,'2023-03-01' order_date UNION ALL
SELECT 2 seq,'2023-04-01' order_date UNION ALL
SELECT 3 seq,'2023-05-01' order_date UNION ALL
SELECT 4 seq,'2023-07-01' order_date UNION ALL
SELECT 5 seq,'2023-08-01' order_date UNION ALL
SELECT 6 seq,'2023-10-01' order_date UNION ALL
SELECT 7 seq,'2023-12-01' order_date
-- UNION ALL SELECT 8 seq,'2023-12-01' order_date 注意这种脏数据需过滤掉
) A
)B
GROUP BY datediff
)C
max_val |
3 |
空、空字符串及NA值
1、查不到数据时,显示为N/A,但结果并不是NULL。
注意:这里的IFNULL(NULL,’Seeme’)并未执行。
2、能查到数据时IFNULL(NULL,’Seeme’)可以执行
3、能查到数据和查不到数据取UNION ALL时结果显然是查到的数据。
SELECT deptno,IFNULL(deptno,'Seeme'),IFNULL(NULL,'Seeme')
FROM dept WHERE deptno = 50
UNION ALL
SELECT deptno,IFNULL(deptno,'Seeme'),IFNULL(NULL,'Seeme')
FROM dept WHERE deptno = 40
4、同理通过length函数,我们可以看出空字符串、空及N/A的区别
空及空字符串的length分别为NULL及0
5、如果想从N/A里查出NULL,可以用如下的写法:
注:该情况仅支持查一个字段。
连接时用ON 1= 1
在拼接宽表的单个指标时可以通过表关联ON 1= 1,这种方式主要是用于数据的拼接。
比如将A结果集的dname和B结果集里的sal合并出两列。
SELECT A.dname,B.sal
FROM
(SELECT dname FROM dept WHERE deptno = 1
) A
LEFT JOIN
(
SELECT sal FROM emp WHERE empno = 7369
)B
ON 1= 1
dname | sal |
张三 | 800.00 |