oracle sql 相关
过滤数据
在SELECT语句内,通过在WHERE子句中指定搜索条件来过滤数据。紧接在表名(FROM子句)后面指定WHERE子句。
SELECT prod_name, prod_price
FROM products
WHERE prod_price = 2.50;
在同时使用ORDER BY和WHERE子句时,要确保ORDER BY出现在WHERE后面
where的运算符
运算符 | 描述 |
---|---|
= | 等于 |
!= | 不等于 |
< | 小于 |
between | 两个值之间 |
>= | 大于或等于 |
默认的情况下,oracle是区分大小写的,但是具体的需要看服务器的配置,可以使用字符串操作函数来匹配这些方法。
把值与一个字符串数据类型(Datatype)的列进行比较需要定界的引号。数值列使用的值不要用引号括住。
当使用BETWEEN时,必须指定两个值:范围的开头和结尾,还必须通过AND关键字分隔两个值。BETWEEN匹配范围中的所有值,包括指定范围的起始值和结束值。
检查没有值的情况
NULL:没有值(No Value),与包含0、空字符串或者只包含空格的字段相对。
SELECT prod_name
FROM products
WHERE prod_price IS NULL;
使用and追加条件
使用and可以按多列过滤数据,只有多列的筛选条件都满足才会返回结果。
使用or运算符
or运算符与and运算符相反,检索与任意一个条件匹配的行。
理解求值顺序
当or与and并用的时候,and具有更高的运算优先级,建议使用圆括号组合相关的运算符
In的使用
-
在处理有效选项很多的列表时,IN运算符语法要清晰得多,并且更容易读取。
-
当使用IN运算符时,求值的顺序更容易管理(因为使用的运算符比较少)。
-
IN运算符几乎总会比OR运算符的列表执行得更快(尽管对于像这里使用的非常短的列表,将不会看到任何性能差别)。
-
IN的最大的优点是:IN运算符可以包含另一条SELECT语句,从而允许构建高度动态的WHERE子句。
IN:WHERE中使用的一个关键字用于指定一个值列表,将使用OR比较与之匹配。
NOT否定其后的条件
结合使用NOT与IN运算符将很容易查找所有与条件列表不匹配的行。
使用通配符过滤
使用%进行搜索
%匹配出现任意次数的任意字符。除了匹配1个或多个字符之外,%还可匹配0个字符,%在搜索模式中的指定位置代表0个、1个或多个字符。
例如:查找以单词jet开头的产品(这里大小写看数据库服务器的设置)
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE 'Jet%'
下划线_通配符
_的用法和%一样,但是只匹配单个字符。
%可以匹配0个字符,与%不同,_总是匹配一个字符——不多也不少。
正则表达式
oracle的正则表达式区分大小写。
LIKE匹配整个列。如果要匹配的文本存在于列值的中间,LIKE将不会找到它,并且不会返回行(除非使用通配符)。另一方面,REGEXP_LIKE()会在列值内寻找匹配,因此如果要匹配的文本存在于列值的中间,REGEXP_LIKE()将会找到它,并且将返回行。这是一个非常重要的区别。
要搜索两个字符串之一(其中一个或另一个字符串),可以使用|,
SELECT prod_name
FROM products
WHERE REGEXP_LIKE(prod_name, '1000|2000')
ORDER BY prod_name;
这里使用正则表达式1000|2000。|是正则表达式OR运算符,它表示“匹配其中一个或另一个”,因此1000和2000都会匹配,并且会返回它们。
.用于匹配任意单个字符。但是,如果你只想匹配特定的字符,则该怎么办?可以通过指定一组用[和]括住的字符来执行该任务
还可以对字符集取反。也就是说,它们将匹配除指定字符以外的其他任何字符。要对字符集取反,可以在字符集的开头放置一个^。因此,[123]将匹配字符1、2或3,而[还可以对字符集取反。也就是说,它们将匹配除指定字符以外的其他任何字符。要对字符集取反,可以在字符集的开头放置一个^。因此,[123]将匹配字符1、2或3,而[^123]将匹配除这些字符以外的其他任何字符。
[1-3]和[6-9]也是有效的范围。此外,范围也不需要是数字,因此[a-z]将匹配任意字母字符。
要匹配特殊字符,必须在它们前面放置\。因此,-意味着查找-,.则表示查找.
类别 | 描述 |
---|---|
\d | 任意数字 |
\D | 任意非数字 |
\w | 任意字母 |
\W | 任意非字母或数字字符(等同于【^a-zA-Z0-9】) |
\s | 任意空白字符 |
\S | 任意非空白字符 |
^有两种用法:在字符集内(使用[和]定义),使用它来对字符集取反;否则,将把它用于指示字符串的开头。
创建计算字段
连接字段
连接(Concatenate):把值连接在一起(通过相互追加它们),构成单个较长的值。
许多DBMS允许使用+连接字符串。Oracle则不然,必须使用||进行连接。
Trim()函数:除了RTrim()函数(如刚才所示的,它用于修剪字符串的右边)之外,PL/SQL还支持使用LTrim()(用于修剪字符串的左边)和Trim()(用于修剪左右两边)。这些函数均用于修剪空格。
使用别名
使用数据操作函数
大多数的sql都支持以下类型的函数:
-
文本函数:用于操作文本字符串(例如,修剪或填充值,以及把值转换成大写和小写形式)。
-
数值函数:用于对数值型数据执行数学运算(例如,返回绝对值以及执行代数计算)。
-
日期和时间函数:用于操作日期和时间值,以及从这些值中提取特定的部分(例如,返回日期之间的差值以及检查日期有效性)。
-
系统函数:返回特定于所用的DBMS的信息(例如,返回用户登录信息或者检查版本的详细信息)。
文本操作函数
- MySQL: SUBSTR( ), SUBSTRING( )
- Oracle: SUBSTR( )
- SQL Server: SUBSTRING( )
函数 | 描述 |
---|---|
Length() | 返回字符串长度 |
Lower() | 把字符串转换成小写 |
LPad() | 在字符串左边填充空格 |
LTrim() | 从字符串左边修剪空格 |
RPad() | 在字符串右边填充空格 |
RTrim() | 在字符串右边修剪空格 |
SubString() | 返回字符串内的字串 |
Upper() | 把字符串转成大写 |
日期和时间操作函数
Oracle使用4位的年份。如果提供一个两位的年份,Oracle也许不会像你期望的那样处理它。因此,总是使用完整的4位年份,使得Oracle可以理解你的输入。
SELECT cust_id, order_num
FROM orders
WHERE order_date = TO_DATE('2015-02-01', 'yyyy-mm-dd');
Extract()用于提取日期和时间的某些部分,允许只处理YEAR、MONTH、DAY、HOUR、MINUTE和SECOND。
SELECT cust_id, order_num
FROM orders
WHERE Extract(Year FROM order_date) = 2015
AND Extract(Month FROM order_date) = 2
#日期到字符操作
select sysdate,to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual
#字符到日期操作
select to_date('2003-10-17 21:15:37','yyyy-mm-dd hh24:mi:ss') from dual
to_date()里面的格式
Year:
yy two digits 两位年 显示值:07
yyy three digits 三位年 显示值:007
yyyy four digits 四位年 显示值:2007
Month:
mm number 两位月 显示值:11
mon abbreviated 字符集表示 显示值:11月,若是英文版,显示nov
month spelled out 字符集表示 显示值:11月,若是英文版,显示november
Day:
dd number 当月第几天 显示值:02
ddd number 当年第几天 显示值:02
dy abbreviated 当周第几天简写 显示值:星期五,若是英文版,显示fri
day spelled out 当周第几天全写 显示值:星期五,若是英文版,显示friday
ddspth spelled out, ordinal twelfth
Hour:
hh two digits 12小时进制 显示值:01
hh24 two digits 24小时进制 显示值:13
Minute:
mi two digits 60进制 显示值:45
Second:
ss two digits 60进制 显示值:25
数值操作函数
常见的数值操作函数:
函数 | 描述 |
---|---|
Abs() | 绝对值 |
cos() | 余弦 |
exp() | 返回指定数字的指数值 |
mod() | 返回除法运算的余数 |
sin() | 返回指定角度的三角正弦值 |
sqrt() | 返回平方根 |
tan() | 三角正切值 |
聚合函数 汇总数据
avg() 求均值
count()
max()
min()
sum()
AVG()只可能用于确定特定数字列的平均值,并且必须把该列名指定为函数参数。要获得多个列的平均值,就必须使用多个AVG()函数。
AVG()只可能用于确定特定数字列的平均值,并且必须把该列名指定为函数参数。要获得多个列的平均值,就必须使用多个AVG()函数。
- 使用COUNT(*)统计表中的行数,无论列包含的是数值还是NULL值;
- 使用COUNT(column)统计在特定列中具有值(忽略NULL值)的行数。
所有的聚合函数都可用于使用标准的数学运算符在多个列上执行计算,如示例中所示使用sum()函数的时候。
SELECT SUM(item_price*quantity) AS total_price
FROM orderitems
WHERE order_num = 20005;
distinct 独特值的聚合
使用distinct可以只在单一独特的值上面进行聚合计算
如果指定列名,DISTINCT将只能与COUNT()结合使用。DISTINCT不能与COUNT(*)结合使用,因此不允许COUNT(DISTINCT *),它会生成一个错误。类似地,必须把DISTINCT用于列名,而不能用于计算或表达式。
数据分组
使用group by 和having对数据进行分组
- GROUP BY子句可以根据需要包含许多列。它允许嵌套分组,对数据的分组方式进行更细粒度的控制。
- 如果在GROUP BY子句中具有嵌套的分组,就会在最后指定的分组上汇总数据。换句话说,在建立分组时,将会把指定的所有列放在一起评估(因此,将不会针对每个单独的列取回数据)。
- GROUP BY中列出的每一列都必须是一个检索的列或者有效的表达式(而不是一个聚合函数)。如果在SELECT中使用一个表达式,那么必须在GROUP BY中指定相同的表达式。不能使用别名。
- 除了聚合计算语句之外,SELECT语句中的每一列都应该出现在GROUP BY子句中。
- 如果分组列包含一个具有NULL值的行,将返回NULL,作为一个分组。如果有多行具有NULL值,则将把它们都分组在一起。
- GROUP BY子句必须出现在WHERE子句的后面和ORDER BY子句的前面。
过滤分组
where无法用于过滤分组,所以有了having
SELECT cust_id, COUNT(*) AS orders
FROM orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;
WHERE过滤发生在数据分组之前,而HAVING过滤则发生在数据分组之后。这是一个重要的区别,被WHERE子句删除的行不会包括在分组中。这可能会改变计算值,基于HAVING子句中使用的那些值,它反过来又可能影响哪些分组将会被过滤。
使用子查询
SELECT cust_name,cust_state,
(SELECT COUNT(*)
FROM orders
WHERE orders.cust_id = customers.cust_id) AS orders
FROM customers
ORDER BY cust_name;
连接表
as的用法
oracle中as不能用于指定表的别名,在o’racle中指定表的别名只需要在原有表名和别名之间用空格隔开即可
但是可以用与指定列的别名。
#针对单个别名
with tmp as (select * from table1)
#多个别名
with
tmp1 as(select * from table1),
tmp2 as(select * from table2)
select * from tmp1,tmp2 where tmp1.id=tmp2.uid
自连接
假设某一件产品(商品ID为DTNTR)被发现存在问题,因此你想知道由同一个供应商制造的所有产品,以确定它们是否也存在这个问题。
第一种方法:
SELECT prod_id, prod_name FROM products
WHERE vend_id = (SELECT vend_id FROM products WHERE prod_id = 'DTNTR');
第二种:
SELECT p1.prod_id, p1.prod_name FROM products p1, products p2
WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'DTNTR';
#等价于
SELECT p1.prod_id, p1.prod_name FROM products p1
INNER JOIN products p2 ON p1.vend_id = p2.vend_id WHERE p2.prod_id = 'DTNTR';
外连接
-
统计每位顾客下了多少份订单,包括还没有下订单的顾客。
-
列出所有产品的订购数量,包括没有被任何人订购的产品。
-
在将还没有下订单的顾客考虑在内的情况下,计算平均的销售规模。
在所有这些示例中,连接都包括了在相关表中没有关联行的表行。这类连接称为外连接(Outer Join)。
外连接分为外左连接(left outer join)和外右连接(right outer join)
inner join 就等于 join left outer join 与 left join 等价, 一般写成left join right outer join 与 right join等价,一般写成right join
交叉连接:返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积
语句1:隐式的交叉连接,没有CROSS JOIN。
SELECT O.ID, O.ORDER_NUMBER, C.ID, C.NAME
FROM ORDERS O , CUSTOMERS C
WHERE O.ID=1;
语句2:显式的交叉连接,使用CROSS JOIN。
SELECT O.ID,O.ORDER_NUMBER,C.ID,
C.NAME
FROM ORDERS O CROSS JOIN CUSTOMERS C
WHERE O.ID=1;
组合查询
使用union将多个select语句合成一个
join可以看作是在列上的合并,union可以看作是在行上的合并。
- UNION必须由两个或更多的SELECT语句组成,每条语句都用关键字UNION分隔开(因此,如果组合4条SELECT语句,则要使用3个UNION关键字)。
- UNION中的每个查询都必须包含相同的列、表达式或聚合函数(尽管列不必以相同的顺序列出)。
- 列数据类型必须是兼容的。它们不必是完全相同的类型,但是它们必须是Oracle可以隐式转换的类型(例如,不同的数值类型或者不同的日期类型)。
UNION将从查询结果集中自动删除任何重复的行。如果想要保留重复的行,就用union all 代替union
SELECT语句的输出是使用ORDER BY子句进行排序的。当利用UNION组合查询时,只能使用一个ORDER BY子句,并且它必须出现在最后一个SELECT语句之后。以一种方式对结果集的一部分进行排序并以另一种方式对另一部分进行排序,这种做法极少是有意义的,因此不允许使用多个ORDER BY子句。
插入数据
常用的sql语句有:select、insert、update和delete
insert
INSERT INTO Customers
VALUES(10006,
'Pep E.LaPew',
'100 Main Street',
'Los Angeles',
'CA',
'90046',
'USA');
insert 一般向表中添加特定的值,但是也可以与select连用。
INSERT INTO customers(cust_id, cust_contact, cust_email)
SELECT cust_id, cust_contact, cust_email
FROM custnew;
Oracle甚至不会注意由SELECT返回的列名。相反,使用的是列位置,因此SELECT中的第一列(无论它的名称是什么)将用于填充第一个指定的表列,等等。
更新和删除数据
可以使用update更新或修改表中的数据。
UPDATE customers
SET cust_email = 'elmer@fudd.com'
WHERE cust_id = 10005;
在更新多列时,将只使用单个SET命令,并用逗号隔开每个column = value对(在最后一个列后面无需指定逗号)。
UPDATE customers
SET cust_name = 'The Fudds',
cust_email = 'elmer@fudd.com'
WHERE cust_id = 10005;
删除数据 delete
DELETE FROM customers
WHERE cust_id = 10006;
创建和操作表
使用create table创建表,必须指定两个信息:
- 在create table后面指定新表的名称
- 用逗号隔开表列的名称和定义
CREATE TABLE customers
(
cust_id int NOT NULL ,
cust_name char(50) NOT NULL ,
cust_address char(50) NULL ,
cust_city int default 1 Not NULL ,
cust_state char(5) NULL ,
cust_zip char(10) NULL ,
cust_country char(50) NULL ,
cust_contact char(50) NULL ,
cust_email char(255) NULL
);
不要把NULL值与空字符串混淆。NULL值意指缺少值,它不是空字符串。如果指定“’ '”(两个单引号之间没有任何内容),这在NOT NULL列中是允许的。空字符串是一个有效值,它不是没有值。NULL值是利用关键字NULL指定的,而不用利用空字符串指定的。
更新表的结构 (主键外键)
可以使用alter table更新表的结构
-
需要指定要改变的表的名称
-
要执行的更改列表
添加一列 alter table user add phone char(20); 删除一列 alter table user drop column phone; 可以在创建表之后再执行添加主键 ALTER TABLE customers ADD constraint pk_customers PRIMARY KEY (cust_id); 添加外键 ALTER TABLE orderitems ADD constraint fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num);
删除表
使用drop table删除表
视图用于简化代码
CREATE VIEW orderitemsexpanded AS
SELECT order_num,
prod_id,
quantity,
item_price,
quantity*item_price AS expanded_price
FROM orderitems;
使用触发器
创建触发器需要的4组信息:
- 触发器的名称
- 触发器要关联的表
- 触发器应该响应的动作(DELETE、INSERT或UPDATE)
- 何时应该执行触发器(处理前或处理后)
触发器使用create trigger
create or replace trigger order_after_insert
after insert on table_orders
for each row
begin
end;
删除一个触发器,使用drop命令
drop trigger order_after_insert
一个完整的例子
CREATE OR REPLACE TRIGGER orders_after_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO orders_log(changed_on, change_type, order_num)
VALUES(SYSDATE, 'A', :NEW.order_num);
END;
- 在UPDATE触发器代码中,可以引用一个名为:OLD的虚拟表访问以前(在UPDATE语句之前)的值,以及引用:NEW访问新更新的值。
- 在BEFORE UPDATE触发器中,:NEW中的值也可以更新(允许更改将要在UPDATE语句中使用的值)。
- :OLD中的值都是只读的,不能被更新。
用于确保状态缩写词都是大写
CREATE OR REPLACE TRIGGER customers_before_update
BEFORE UPDATE ON customers
FOR EACH ROW
BEGIN
:NEW.cust_state := Upper(:NEW.cust_state);
END;
管理事务处理
事务管理是一种机制,用于管理必须成批的SQL操作,以确保数据库永远不会包含不完整的操作结果。
Rollback撤销回滚
SELECT * FROM orders_log;
DELETE FROM orders_log;
SELECT * FROM orders_log;
ROLLBACK;
SELECT * FROM orders_log;
这个示例首先显示orders_logs表的内容(这个表是在第24章“使用触发器”中创建的)。首先,执行一条SELECT语句,显示表不是空的,然后利用DELETE语句删除所有的行。另一条SELECT语句验证orders_logs表的确是空的,然后使用ROLLBACK语句回滚所有的语句,最后一条SELECT语句显示表不再是空的。
commit强制提交
使用保存点
使用savepoint创建保存点
savepoint delete1;
回滚的时候可以返回这个保存点
rollback savepoint delete1;
管理安全性
创建用户
创建一个密码为password的test用户·
create user test identified by "password"
删除用户
drop user test
oracle不允许重命名,只有先drop再create
设置访问权限
使用grant授权,要指定:授权的特权,授权的项目,用户名
grant select,insert,update on table1 to test
撤销权限
revoke insert, select on table1 from test
特权 | 描述 |
---|---|
all | |
alter | |
delete | |
index | create index和drop index的使用 |
insert | |
references | 创建约束 |
select |
更改用户密码
可以使用alter user 语句
alter user test identified by "pass"
oracle查询优化
单表查询
空值NULL相关
null 不能用=进行判断,需要用 is null 判断
将空值转换为实际值
nvl(COMMISSION_PCT,0)
如果第一个参数为null,则返回第二个参数
如果第一个参数为非null,则返回第一个参数
COALESCE(EXPR1,EXPR2,EXPR3...EXPRn)
从左往右数,遇到第一个非null值,则返回该非null值。 多层判断
第一点区别:从上面可以知道,nvl只适合于两个参数的,COALESCE适合于多个参数。
第二点区别:COALESCE里的所有参数类型必须保持一致,nvl可以不一致。
拼接列
oracle使用||拼接列
mysql有concat函数
在select中使用条件逻辑
select name,sal
case
when a<100 then '底'
else 'ok'
end as pingjia from table1 where dep=njupt
返回限制的行数
可以使用rownum来过滤,rownum会对每条数据做一个标识。
存在的问题:rownum依次对数据做标识,只有先把前面的数据取出来,才能取到第n行。
例子:取第二行的数据
select * from
(select rownum as sn,emp.* from emp where sn<=2) a
where a.sn=2;
查询语句的执行顺序 1、select 2、rownum 3、order by
模糊查询与转义字符
#对于通配符可以使用转移字符\ 会找出如 _bcded 和_bcdes等
select * from table1 where a like '\_bcd%' escape '\'
#对于包含转移字符,及查找值为_\abcd的字符 会找出如:_\bced和 _\bcefs等
select * from table1
where a like '_\\bce%' escape '\';
给查询结果排序
通过order by排序的时候,除了可以指定列名,还可以直接用1,2,3这种指定依据第几列来排
但是使用数据来代替列位置只能用于order by语句中,其他地方都不可以用。
排序有两个关键字 asc 升序 默认的; desc 降序 ,需要指定。
按照子串排序
select name,phone,substr(phone,-4) as 尾号
from employ where rownum<=5 order by 3;
字符串替换
select translate('ab 你好 cdef','abcdef','123456') as new from dul ;
结果:12 你好 3456
translate(expr,from,to) from和to以字符为单位,一一替换
设定空值最前还是最后
通过null first和null last设置
select ename,sal from emp order by 2 null first;
根据条件取不同列中的值进行排序
可以在查询中生成新的一列,用于排序
select emp as 编码,ename as 姓名,
case when sal>=1000 and sal<2000 then 1 else 2 end as 级别,
sal as 工资
from emp where dep=30 order by 3,4;
操作多个表
in exists和inner join
in的使用
select a1,a2,a3,a4 from emp
where (a2,a3,a4) in (select a2,a3,a4 from emp2)
exists的使用
select a1,a2,a3,a4 from emp a
where exists (select null from emp2 b
where a.a2=b.a2
and a.a3=b.a3
and a.a4=b.a4);
join的使用方法
select a1,a2,a3,a4 from emp a
join emp2 b on(a.a2=b.a2
and a.a3=b.a3
and a.a4=b.a4)
自关联
两次查询一个表emp,分别取不同的别名,可以当作是两个表。
更新与插入表
- 如果insert语句没有包含默认值的列,那么会添加默认值
- 如果包含有默认值的列,需要用default关键字才会添加默认值
- 如果已经显示设定了NULL或者其他值,那么不会使用默认值。
使用数字
聚集函数默认是会忽略空值,对于sum,max没有影响,但是对于count和avg有影响。
生成累计和
使用over窗口函数计算
想要计算员工的累加工资,看员工人数和工资支出的关系
select empno as 编号,
ename as 姓名,
sal as 人工成本,
sum(sal) over(order by empno) as 成本累计
from emp where dep=30 order by empno
返回各个部门工资前三名的员工
这种存在歧义,有三个分析函数可以实现
select deptno,
empno,
sal,
row_number() over(partition by deptno order by sal desc) as row_number,
rank() over(partition by deptno order by sal desc) as rank,
dense_rank() over(partition by deptno order by sal desc) as dense_rank
from emp where deptno in (20,30,40) order by 1,3,desc
当sal出现重复数据的时候:
row_number()仍然是1,2,3
rank() 相同的工资回生成相同的序号,但是会跳号,会出现1,1,3,3,5这种
dense_rank() 相同的工资会生成相同的序号,且不会跳号,会依次出现1,1,2,2,3这种
求总和的百分比
求各个部门工资之和,以及对应之和占总工资的比例
select deptno as 部门,
工资合计,总合计,
round((工资合计/总合计)*100,2) as 工资比例
from(select deptno,sum(工资合计) over() as 总合计
from (select deptno, sum(sal) as 工资合计 from emp group by deptno) x
)y
order by 1;
用到over以及sum函数,当over后面不加任何内容,就是对所有数据进行汇总
日期运算
在oracle中,date类型可以直接加减天数,而加减月份需要用到add_months函数
select hirdate as 日期,
hirdate-5 as 减5天,
add_months(hirdate,-5) as 减5个月,
add_months(hirdate,-5*12) as 减5年
hirdate-5/24/60 as 减5秒
from emp where rownum<=1;
日期间隔之年月日
加减月份用add_months,计算月份间隔要用months_between
select max_hd-min_hd 间隔天,
months_between(max_hd,min_hd) 间隔月,
months_between(max_hd,min_hd)/12 间隔年
from (select min(hirdate) min_hd,max(hirdate) max_hd from emp) x;
当前记录和下一条记录之间相差的天数
lead(hirdate) over(order by hirdate) next_hd 将下一条提到同一行
lag(hirdate) over(order by hirdate) lag_hd 将上一条提到同一行
日期操作
几个常见的取值方式
select date1,
to_number(to_char(date1,'hh24')) as 时,
to_number(to_char(date1,'mi')) as 分,
to_number(to_char(date1,'ss')) as 秒,
to_number(to_char(date1,'mm')) as 月,
to_number(to_char(date1,'dd')) as 日,
to_number(to_char(date1,'ddd')) as 年内第几天,#325
trunc(to_char(date1,'dd')) as 一天之始,#1980-12-17
trunc(to_char(date1,'day')) as 周初,#1980-12-14
trunc(to_char(date1,'mm')) as 月初,#1980-12-01
last_day(date1) 月末,#1980-12-31 05:20:30
to_char(date1,'month') 月份 #显示12月
最后的结果:
date1 | 时 | 分 | 秒 | 月 | 日 | 年内第几天 | 一天之始 | 周初 | 月初 | 月末 |
---|---|---|---|---|---|---|---|---|---|---|
1980-12-17 05:20:30 | 5 | 20 | 30 | 12 | 17 | 352 | 1980-12-17 | 1980-12-14 | 1980-12-01 | 1980-12-31 05:20:30 |
extract函数
和to_char函数一样,extract函数可以提取时间字段中的年月日时分秒,但是extract的返回值是number类型的
to_cahr()截取年月日时分秒所用的是yyyy、mm、dd这种模式,
而extract()截取年月日时分秒所用的是它们相应的英文year、month、day
日期字段也是有格式的 extract不能截取date类型的时、分、秒 但是to_char可以
extract能截取interval类型的信息,但是to_char不行
需要搭配timestamp来获取时间 systimestamp来获取秒
select extract(year from date1) year,
extract(hour from timestamp'2021-11-20 20:18:20') hour,
extract(second from systimestamp'2021-11-20 20:20:10') secomd
高级查找
给结果分页
例如查找员工工资6-10行的数据
select rn as 序号,ename, sal from(
select rownum as rn,sal,enmae
from(
select sal,ename from emp where sal is not null order by sal) x
where rownum <=10)y
where rn>=6;
跳过表中的n行
通过rownum和mod函数即可实现
rownum是数据库独特的关键字
select name,sal,mod(rn,3) as m from
(select rownum as rn, sal,name from
(select name,sal from emp order by name) x) y
where mod(rn,3) =1;
牛客网sql做题笔记
1、查找最晚入职员工信息
有一个employees表
select * from employees order by hire_date desc limit 1;
#第二种解法
SELECT * FROM employees
WHERE hire_date = (SELECT MAX(hire_date) FROM employees)
2、查找入职时间排名倒数第三位的员工的所有信息
select * from employees
where hire_date =(select distinct hire_date from employees
order by hire_date desc limit 2,1)
//第二种解法
select emp_no,birth_date,first_name,last_name,gender,hire_date from(
select *,dense_rank() over(order by hire_date desc) as daynum
from employees) as t
where daynum=3
LIMIT m,n : 表示从第m+1条开始,取n条数据;
row_number()仍然是1,2,3
rank() 相同的工资回生成相同的序号,但是会跳号,会出现1,1,3,3,5这种
dense_rank() 相同的工资会生成相同的序号,且不会跳号,会依次出现1,1,2,2,3这种
3、查找当前薪水详情以及部门编号
有一个全部员工的薪水表salaries简况如下:
emp_no | salary | from_date | to_date |
---|---|---|---|
10001 | 88958 | 2002-06-22 | 9999-01-01 |
10003 | 43311 | 2001-12-01 | 9999-01-01 |
有一个各个部门的领导表dept_manager简况如下:
dept_no | emp_no | to_date |
---|---|---|
d001 | 10001 | 9999-01-01 |
d002 | 10003 | 9999-01-01 |
请你查找各个部门当前领导的薪水详情以及其对应部门编号dept_no,输出结果以salaries.emp_no升序排序,并且请注意输出结果里面dept_no列是最后一列,以上例子输出如下:
emp_no | salary | from_date | to_date | dept_no |
---|---|---|---|---|
10001 | 88958 | 2002-06-22 | 9999-01-01 | d001 |
10003 | 43311 | 2001-12-01 | 9999-01-01 | d002 |
select a.emp_no,a.salary,a.from_date,a.to_date,b.dept_no from salariess a
join dept_manager b
on a.emp_no=b.emp_no
order by a.emp_no
4、查找已分配部门的员工的信息
有一个员工表,employees简况如下:
emp_no | birth_date | first_name | last_name | gender | hire_date |
---|---|---|---|---|---|
10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 |
10002 | 1964-06-02 | Bezalel | Simmel | F | 1985-11-21 |
10004 | 1954-05-01 | Christian | Koblick | M | 1986-12-01 |
有一个部门表,dept_emp简况如下:
emp_no | dept_no | from_date | to_date |
---|---|---|---|
10001 | d001 | 1986-06-26 | 9999-01-01 |
10002 | d002 | 1989-08-03 | 9999-01-01 |
请你查找所有已经分配部门的员工的last_name和first_name以及dept_no,未分配的部门的员工不显示,以上例子如下:
last_name | first_name | dept_no |
---|---|---|
Facello | Georgi | d001 |
Simmel | Bezalel | d002 |
select a.last_name,a.first_name,b.dept_no from employees a,dept_emp b
where a.emp_no=b.emp_no
5、所有员工信息
查找所有已经分配部门的员工的last_name和first_name以及dept_no,也包括暂时没有分配具体部门的员工
select last_name,first_name,dept_no from
employees a left join dept_emp b
on a.emp_no=b.emp_no
6、查找薪水记录超过15条的员工信息
select emp_no,count(*) as t from salaries e group by emp_no
having t>15
注意:由于where的执行在聚合函数之前,所以不可以用where t>15
7、所有员工的薪水,相同的薪水只显示一次,并按逆序显示
select distinct salary from salaries order by salary desc
8、获取所有非manager的员工emp_no
有一个员工表employees简况如下:
emp_no | birth_date | first_name | last_name | gender | hire_date |
---|---|---|---|---|---|
10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 |
有一个部门领导表dept_manager简况如下:
dept_no | emp_no | from_date | to_date |
---|---|---|---|
d001 | 10002 | 1996-08-03 | 9999-01-01 |
找出所有非部门领导的员工emp_no
select emp_no from employees a
where a.emp_no not in (select emp_no from dept_manager)
像in 这个后面的括号,不是一个视图之类的也不是表,不用加别名
9、获取所有员工当前的manager
有一个员工employees表简况如下:
emp_no | birth_date | first_name | last_name | gender | hire_date |
---|---|---|---|---|---|
10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 |
有一个部门经理表dept_manager简况如下:
dept_no | emp_no | from_date | to_date |
---|---|---|---|
d001 | 10002 | 1996-08-03 | 9999-01-01 |
获取所有的员工和员工对应的经理,如果员工本身是经理的话则不显示
select a.emp_no,b.emp_no as manager from dept_emp a,dept_manager b
where a.dept_no=b.dept_no and a.emp_no not in (select emp_no from dept_manager)
10、各部门中员工薪水最高的相关信息
select uni.dept_no, uni.emp_no, max_salary.salary
from
(select d.dept_no, s.emp_no, s.salary
from dept_emp d join salaries s
on d.emp_no = s.emp_no
and d.to_date = '9999-01-01'
and s.to_date = '9999-01-01'
) as uni, /* 部门编号,员工编号,当前薪水 */
(select d.dept_no, max(s.salary) as salary
from dept_emp d join salaries s
on d.emp_no = s.emp_no
and d.to_date = '9999-01-01'
and s.to_date = '9999-01-01'
group by d.dept_no
) as max_salary /* 部门编号,当前最高薪水 */
where uni.salary = max_salary.salary
and uni.dept_no = max_salary.dept_no
order by uni.dept_no;
第二种,更简洁的写法,但是没有第一种写法逻辑清楚
select de.dept_no, de.emp_no, s.salary
#部门编号,员工编号,工资
from dept_emp de join salaries s
on de.emp_no = s.emp_no
#查找最高的工资
where s.salary = (select max(s2.salary)
from dept_emp de2 inner join salaries s2
on de2.emp_no = s2.emp_no
where de2.dept_no = de.dept_no
group by de2.dept_no)
order by de.dept_no
11、emp_no为奇数,且last_name不为Mary的员工信息
查找employees表所有emp_no为奇数,且last_name不为Mary的员工信息,并按照hire_date逆序排列
select emp_no,birth_date,first_name,last_name,gender,hire_date from employees b
where mod(emp_no,2)=1 and last_name!='Mary'
order by hire_date desc;
12、统计与titile对应的员工平均工资avg
有一个员工职称表titles简况如下:
emp_no | title | from_date | to_date |
---|---|---|---|
10001 | Senior Engineer | 1986-06-26 | 9999-01-01 |
有一个薪水表salaries简况如下:
emp_no | salary | from_date | to_date |
---|---|---|---|
10001 | 88958 | 1986-06-26 | 9999-01-01 |
统计出各个title类型对应的员工薪水对应的平均工资avg。结果给出title以及平均工资avg,并且以avg升序排序
select title,avg(b.salary) from titles a
join salaries b on a.emp_no=b.emp_no
group by a.title order by avg(b.salary)
13、获取薪水第二多的员工信息
#错误,没有考虑多位员工信息一样。
select emp_no,salary from salaries order by salary desc limit 1,1
#看是否加distinct 唯一值
select emp_no,salary from salaries
where salary =(select salary from salaries order by salary desc limit 1,1)
如果要求不用order by 完成
select s.emp_no, s.salary, e.last_name, e.first_name
from salaries s join employees e
on s.emp_no = e.emp_no
where s.salary = -- 第三步: 将第二高工资作为查询条件
(
select max(salary) -- 第二步: 查出除了原表最高工资以外的最高工资(第二高工资)
from salaries
where salary <
(
select max(salary) -- 第一步: 查出原表最高工资
from salaries
)
)
19、获得员工和部门关系
有一个员工表employees简况如下:
emp_no | birth_date | first_name | last_name | gender | hire_date |
---|---|---|---|---|---|
10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 |
有一个部门表departments表简况如下:
dept_no | dept_name |
---|---|
d001 | Marketing |
有一个,部门员工关系表dept_emp简况如下:
emp_no | dept_no | from_date | to_date |
---|---|---|---|
10001 | d001 | 1986-06-26 | 9999-01-01 |
查找所有员工的last_name和first_name以及对应的dept_name,也包括暂时没有分配部门的员工
select a.last_name,a.first_name,c.dept_name from employees a
left join dept_emp b
on a.emp_no=b.emp_no
left join departments c
on b.dept_no=c.dept_no
21、入职以来的薪水涨幅
有一个员工表employees简况如下:
emp_no | birth_date | first_name | last_name | gender | hire_date |
---|---|---|---|---|---|
10001 | 1953-09-02 | Georgi | Facello | M | 2001-06-22 |
有一个薪水表salaries简况如下:
emp_no | salary | from_date | to_date |
---|---|---|---|
10001 | 85097 | 2001-06-22 | 2002-06-22 |
查找在职员工自入职以来的薪水涨幅情况,给出在职员工编号emp_no以及其对应的薪水涨幅growth,并按照growth进行升序
SELECT sCurrent.emp_no, (sCurrent.salary-sStart.salary) AS growth
FROM (SELECT s.emp_no, s.salary FROM employees e, salaries s
WHERE e.emp_no = s.emp_no AND s.to_date = '9999-01-01') AS sCurrent,
(SELECT s.emp_no, s.salary FROM employees e, salaries s
WHERE e.emp_no = s.emp_no AND s.from_date = e.hire_date) AS sStart
WHERE sCurrent.emp_no = sStart.emp_no
ORDER BY growth
第二种写法
with t as (select a.emp_no,salary,from_date,to_date,hire_date from employees a
join salaries b on a.emp_no=b.emp_no)
select b.emp_no,(b.salary-t.salary) as growth
from (select emp_no,salary,hire_date from t
where t.to_date='9999-01-01') b,t
where t.emp_no=b.emp_no and b.hire_date=t.from_date
order by growth
22 各个部门的工资数
有一个部门表departments简况如下:
dept_no | dept_name |
---|
有一个,部门员工关系表dept_emp简况如下:
emp_no | dept_no | from_date | to_date |
---|
有一个薪水表salaries简况如下:
emp_no | salary | from_date | to_date |
---|
统计各个部门的工资记录数,给出部门编码dept_no、部门名称dept_name以及部门在salaries表里面有多少条记录sum
第一种写法
select aa.dept_no,aa.dept_name,sum from departments aa
join (select a.dept_no,count(salary) as sum from dept_emp a join salaries b
on a.emp_no=b.emp_no
group by dept_no) c
on aa.dept_no=c.dept_no
第二种写法,不用子查询
SELECT b.dept_no,b.dept_name,count(salary) as sum from
dept_emp a join departments b
on a.dept_no=b.dept_no
join salaries c
on a.emp_no=c.emp_no
group by dept_no,dept_name
order by b.dept_no
23、进行1-N排名
对所有员工的薪水按照salary降序先进行1-N的排名,如果salary相同,再按照emp_no升序排列
select emp_no,salary,dense_rank() over(order by salary desc) t_rank
from salaries order by salary desc,t_rank
注意语法:
dense_rank() over(partition by a order by b desc)
24、非manager员工薪水情况
有一个员工表employees:
emp_no | birth_date | first_name | last_name | gender | hire_date |
---|
有一个,部门员工关系表dept_emp简况如下:
emp_no | dept_no | from_date | to_date |
---|
有一个部门经理表dept_manager简况如下:
dept_no | emp_no | from_date | to_date |
---|
有一个薪水表salaries简况如下:
emp_no | salary | from_date | to_date |
---|
获取所有非manager员工薪水情况,给出dept_no、emp_no以及salary
select b.dept_no,a.emp_no,salary
from employees a join dept_emp b
on a.emp_no=b.emp_no and a.emp_no not in (select emp_no from dept_manager)
join salaries c
on a.emp_no = c.emp_no
25、获取比主管薪资高的员工信息
with t as(select dept_no,de.emp_no as manager_no,salary as manager_salary
from dept_manager de join salaries aa on de.emp_no=aa.emp_no)
select d.emp_no,manager_no,salary as emp_salary,manager_salary
from dept_emp d join salaries sa
on d.emp_no=sa.emp_no and d.emp_no not in(select manager_no from t)
join t
on d.dept_no=t.dept_no and sa.salary>t.manager_salary
26、各部门员工的title类型的数目
有一个部门表departments简况如下:
dept_no | dept_name |
---|
有一个,部门员工关系表dept_emp简况如下:
emp_no | dept_no | from_date | to_date |
---|
有一个职称表titles简况如下:
emp_no | title | form_date | to_date |
---|
汇总各个部门当前员工的title类型的分配数目,即结果给出部门编号dept_no、dept_name、其部门下所有的员工的title以及该类型title对应的数目count,结果按照dept_no升序排序,dept_no一样的再按title升序排序
select gx.dept_no,dept_name,title,count(*) as count
from departments de join dept_emp gx
on de.dept_no=gx.dept_no
join titles t
on gx.emp_no=t.emp_no
group by gx.dept_no,dept_name,title
order by gx.dept_no,title
29、查找未分类的电影和名称
select f.film_id,title from film f
left join film_category fc
on f.film_id = fc.film_id
where fc.category_id is NULL
1、用 LEFT JOIN 连接 film 和 film_category,限定条件为 f.film_id = fc.film_id,即连接电影id和电影分类id,如果电影没有分类,则电影分类id显示null
2、再用 WHERE 来限定条件 fc.category_id IS NULL 选出没分类的电影
30、子查询找出电影描述
使用子查询的方式找出属于Action分类的所有电影对应的title,description
select title,description from film a
where a.film_id in (select film_id from film_category b where b.category_id
in (select category_id from category where name='Action'))
32、合并列作为新列
请将employees表的所有员工的last_name和first_name拼接起来作为Name,中间以一个空格区分。
select concat(last_name,' ',first_name) as Name from employees
第二种写法
select CONCAT(CONCAT(last_name," "),first_name) as name from employees
33、创建表
create table actor(
actor_id smallint(5) not null PRIMARY key comment'主键id',
first_name varchar(45) not null comment'名字',
last_name varchar(45) not null comment'姓氏',
last_update date not null comment'日期'
)
34、批量插入数据
insert into actor(actor_id,first_name,last_name,last_update)
values(1,'PENELOPE','GUINESS','2006-02-15 12:34:33'),
(2,'NICK','WAHLBERG','2006-02-15 12:34:33')
注意语法
insert into table_name(列名1,列名2,…)
values(value1,value2,…),
(value1,value2,…)
35、插入数据,不使用replace
# mysql中常用的三种插入数据的语句:
# insert into表示插入数据,数据库会检查主键,如果出现重复会报错;
# replace into表示插入替换数据,需求表中有PrimaryKey,
# 或者unique索引,如果数据库已经存在数据,则用新数据替换,如果没有数据效果则和insert into一样;
# insert ignore表示,如果中已经存在相同的记录,则忽略当前新数据;
insert ignore into actor values("3","ED","CHASE","2006-02-15 12:34:33");
36、创建表
create table actor_name(
first_name varchar(45) not null comment'名字',
last_name varchar(45) not null comment'姓氏'
)as
select first_name,last_name from actor
37、建立索引
对first_name创建唯一索引uniq_idx_firstname,对last_name创建普通索引idx_lastname
alter table actor add unique index uniq_idx_firstname(first_name);
alter table actor add index idx_lastname(last_name);
38、创建视图
针对actor表创建视图actor_name_view,只包含first_name以及last_name两列,并对这两列重新命名,first_name为first_name_v,last_name修改为last_name_v:
create view actor_name_view
as select first_name as first_name_v,last_name as last_name_v from actor
39、创建索引
针对salaries表emp_no字段创建索引idx_emp_no,查询emp_no为10005,使用强制索引。
select * from salaries
force index (idx_emp_no) where emp_no=10005
使用 sql 语句查询数据的时候,如果表里有好几个索引,mysql 优化器会自己判断使用哪个索引进行搜索。但是,where 条件比较复杂的时候,优化器使用的索引就不一定是最优索引了。
40、增加一列
在last_update后面新增加一列名字为create_date, 类型为datetime, NOT NULL,默认值为’2020-10-01 00:00:00’
alter table actor add column
create_date datetime not null default '2020-10-01 00:00:00'
41、触发器
构造一个触发器audit_log,在向employees_test表中插入一条数据的时候,触发插入相关的数据到audit中。
create trigger audit_log
after insert on employees_test
for each row
begin
insert into audit values(new.id,new.name);
end
在MySQL中,创建触发器语法如下:
CREATE TRIGGER trigger_name
trigger_time trigger_event ON tbl_name
FOR EACH ROW
trigger_stmt
其中:
trigger_name:标识触发器名称,用户自行指定;
trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;
trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;
tbl_name:标识建立触发器的表名,即在哪张表上建立触发器;
trigger_stmt:触发器程序体,可以是一句SQL语句,或者用 BEGIN 和 END 包含的多条语句,每条语句结束要分号结尾。
42、删除重复的记录
在MYSQL里,不能先select一个表的记录,在按此条件进行更新和删除同一个表的记录,
将select得到的结果,再通过中间表select一遍,这样就规避了错误,这个问题只出现于mysql,mssql和oracle不会出现此问题。
delete from titles_test
where id not in
( select * from(
select min(id)
from titles_test
group by emp_no
) as T1
)
43、修改记录
update titles_test
set to_date = null ,from_date='2001-01-01'
where to_date='9999-01-01'
注意,是用,分开,不是用and连接
44、使用replace相关
方法一:全字段更新替换。若REPLACE的新记录中与表中的主键冲突,会替换掉表中的记录
REPLACE INTO titles_test VALUES (5, 10005, 'Senior Engineer', '1986-06-26', '9999-01-01')
方法二:运用REPLACE(X,Y,Z)函数。其中X是要处理的字符串,Y是X中将要被替换的字符串,Z是用来替换Y的字符串,最终返回替换后的字符串。
UPDATE titles_test SET emp_no = REPLACE(emp_no,10001,10005) WHERE id = 5
45、修改表名
alter table titles_test rename titles_2017
46、创建外键约束
alter table audit add constraint foreign key(emp_no)
references employees_test(id)
48、修改员工薪水
写出更新语句,将所有获取奖金的员工当前的(salaries.to_date=‘9999-01-01’)薪水增加10%。(emp_bonus里面的emp_no都是当前获奖的所有员工,不考虑获取的奖金的类型)。
update salaries
set salary=salary*1.1
where to_date='9999-01-01'
and salaries.emp_no in(select emp_no from emp_bonus)
50、连接列
select concat(last_name,"'",first_name) as name from employees
select concat(last_name,'/'',first_name) as name from employees
51、查找逗号出现的次数
只要将原来的逗号换成""无空格,然后前后长度相减即可。
select id,length(string)-length(replace(string,",","")) as cnt from strings
52、按照最后若干个字母排序
将employees中的first_name,并按照first_name最后两个字母升序进行输出。
select first_name from employees
order by substr(first_name,-2)
第二种写法,用right函数
SELECT first_name FROM employees ORDER BY RIGHT(first_name,2);
substr(string,start,length)
参数 | 必须 | 参数说明 |
---|---|---|
string | 必须 | 指定的要截取的字符串 |
start | 必须 | 规定在字符串的何处开始: 正数 - 在字符串的指定位置开始 负数 - 在从字符串结尾的指定位置开始 0 - 在字符串中的第一个字符处开始 |
length | 可选 | 指定要截取的字符串长度,缺省时返回字符表达式的值结束前的全部字符 |
53、按字段汇总
多行转为一行,按照dept_no进行汇总,属于同一个部门的emp_no按照逗号进行连接
SELECT dept_no,group_concat(emp_no) employees
FROM dept_emp GROUP BY dept_no
group_concat(column[order by asc/desc],分割符号) 要配合group by 使用
54、平均工资
不能够用 not in (select max,min)
select avg(salary) as avg_salary from salaries
where salary !=(select min(salary) from salaries where to_date='9999-01-01')
and salary != (select max(salary) from salaries where to_date='9999-01-01')
and to_date='9999-01-01'
55、分页查询
从第n+1个位置开始,取m个
select * from employees limit 5,5;
select * from employees limit n offset m
57、使用exists
select * from employees where not exists
(SELECT emp_no FROM dept_emp WHERE emp_no = employees.emp_no)
59、获得奖金的员工信息
请给出emp_no、first_name、last_name、奖金类型btype、对应的当前薪水情况salary以及奖金金额bonus。bonus结果保留一位小数,输出结果按emp_no升序排序。
SELECT e.emp_no, e.first_name, e.last_name, b.btype, s.salary,
(CASE b.btype
WHEN 1 THEN s.salary * 0.1
WHEN 2 THEN s.salary * 0.2
ELSE s.salary * 0.3 END) AS bonus
FROM employees AS e INNER JOIN emp_bonus AS b ON e.emp_no = b.emp_no
INNER JOIN salaries AS s ON e.emp_no = s.emp_no AND s.to_date = '9999-01-01'
60、累计和
按照salary的累计和running_total,其中running_total为前N个当前( to_date = '9999-01-01')员工的salary累计和
select emp_no,salary,sum(salary) over(order by emp_no) as running_total
from salaries where to_date='9999-01-01'
61、排名为奇数行的first_name
使用自联结
select e.first_name
from employees as e
join (
select first_name,
row_number() over (order by first_name) as rk_name
from employees) as r
on e.first_name = r.first_name
where r.rk_name % 2 = 1
62、出现三次以上相同积分
select number from grade group by number
having count(*)>=3
63、刷题通过的题目排名
根据上表,输出通过的题目的排名,通过题目个数相同的,排名相同,此时按照id升序排列
此刻注意的是,id不能放到over里面排序,会出错,加入over中等于根据number和id排序,
此时dense_rank变成了row_number;1,2,3,4,5
select id,number,dense_rank() over(order by number desc) as t_rank from passing_number
order by t_rank,id
64、找到每个人的任务
需要注意两个表的同一个名称的字段不一定对应
select a.id,name,content from person a
left join task b on a.id=b.person_id
order by a.id
65、异常邮件的概率
第一种写法:
with t as(select a.date,a.type,count(*) as num from email a
where a.send_id not in(select id from user where is_blacklist=1)
and a.receive_id not in(select id from user where is_blacklist=1)
group by a.date,a.type)
select t.date,
round(sum(case t.type when'completed' then 0 else num end)*1.0/sum(num),3) as p
from t group by date
注意点:可以使用case when 配合sum使用
第二种写法:
select email.date, round(
sum(case email.type when'completed' then 0 else 1 end)*1.0/count(email.type),3
) as p
from email
join user as u1 on (email.send_id=u1.id and u1.is_blacklist=0)
join user as u2 on (email.receive_id=u2.id and u2.is_blacklist=0)
group by email.date order by email.date;
66、用户最近的登录日期
select user_id, max(l.date) from login l
group by user_id order by user_id
67、用户最近登录日期二
查询每个用户最近一天登录的日子,用户的名字,以及用户用的设备的名字,并且查询结果按照user的name升序排序
select b.name as u_n,c.name as c_n,a.date from login a
join user b on a.user_id=b.id
join client c on a.client_id=c.id
join (select user_id,max(date) as dd from login group by user_id) d
on a.user_id=d.user_id and a.date=d.dd
order by u_n
注意:是否需要按照某种顺序排序
68、用户最近登录日期三
有一个登录(login)记录表,简况如下:
id | user_id | client_id | date |
---|---|---|---|
1 | 2 | 1 | 2020-10-12 |
查询新登录用户次日成功的留存率,即第1天登陆之后,第2天再次登陆的概率,保存小数点后面3位(3位之后的四舍五入)
(第一天登录的新用户并且第二天也登录的用户)/(总用户)即为新登录用户的次日成功的留存率
总用户如下:
select count(distinct user_id) from login
找到每个用户第一天登陆的日子,其实挺好找,和前面找最近登录的日子差不多,一个是max,一个是min:
select user_id,min(date) from login group by user_id
比如上面查找语句是1,2020-10-12;那么如果找到一个结果为1,2020-10-13的那么是不是就符合结果了,于是可以如下写:
select user_id,date(min(date),'+1 day') from login group by user_id
这样就可以找到所有的在第一天登录的新用户并且第二天也登录的用户,以及第二天的日期。
select
#分母是一个子查询,所以是所有的用户,分子是第二天又登录的用户
round(count(distinct user_id)*1.0/
(select count(distinct user_id) from login) ,3)
from login where (user_id,date)
in (select user_id,DATE_ADD(min(date),INTERVAL 1 DAY)
from login group by user_id);
69、每日新用户登录数
select l1.date,count(distinct l1.user_id)
from login l1 group by l1.date;
这样可以得到每个日期里面,用户登录的数目,比较简单,所以在加一个where判断条件就能从这每个日期里面,用户登录的数目取出哪些是新用户,如下:
select l1.date,count(distinct l1.user_id)
from login l1 where l1.date =(select min(date) from login where user_id=l1.user_id)
group by l1.date;
当这个日期,正好是这个用户登录的最小日期,而且用户id相同时,那么肯定就是这个日期登录的新用户,执行的用例的话,得到的结果应该如下:
2020-10-12|3
2020-10-14|1
但是这样并不能通过用例,因为这样的话,2020-10-13没有新用户登录,应该输出为0的,这个语句却没有输出。但是login表的日期是完整的,所以我们考虑将login表当主表,上面查出来的表左连接到主表,顺序输出,并使用ifnull语句将null变成0,最后再加上一个order by语句,就可以得到题目想要的结果了:
select login.date,ifnull(n1.new_num,0)
from login left join
(select l1.date,count(distinct l1.user_id) as new_num
from login l1
where l1.date = (select min(date) from login where user_id=l1.user_id) group by l1.date) n1
on login.date = n1.date group by login.date order by login.date
70、每个日期新用户的次日留存率
with tmp1 as (select fir_date,count(distinct user_id) cunt
from
(select *,datediff(date,fir_date) cn_days
from
(select *,
first_value(date) over(partition by user_id order by date) fir_date
from login) t1) t2
where cn_days=1
group by fir_date),
tmp2 as (select date, sum(if(rank_d=1,1,0)) new_cun
from
(select *,
row_number() over(partition by user_id order by date) rank_d
from login) t1
group by date)
select date,
round(ifnull(tmp1.cunt / tmp2.new_cun,0),3) p
from tmp2 left join tmp1 on tmp2.date=tmp1.fir_date
order by date
#第二种写法
select date
,ifnull(round((sum(case when (user_id,date)in
(select user_id,date_add(date,interval -1 day)
from login) and
(user_id,date)in (select user_id,min(date)from login group by user_id)
then 1 else 0 end))/
(sum(case when (user_id,date)in
(select user_id,min(date)from login group by user_id)
then 1 else 0 end)),3),0)as p
from login
group by date
order by date;
71、每个人最近登录日期六
select name as u_n,a.date,sum(number) over(partition by name order by a.date) as ps_num from login a
join passing_number b on a.user_id=b.user_id and a.date=b.date
join user c on a.user_id=c.id
order by a.date,u_n