需求
查询用户最后一次登录的信息,每个用户只返回一条最后登录的记录
数据库
-- Create table
create table B_MAC_USB_KEY
(
ID VARCHAR2(64) not null,
REMARK VARCHAR2(2000),
CREATE_BY VARCHAR2(64),
CREATE_DATE DATE default sysdate,
UPDATE_BY VARCHAR2(64),
UPDATE_DATE DATE,
STATUS CHAR(1) default '0',
SNO VARCHAR2(20),
MAC VARCHAR2(100),
BIND_NUM NUMBER,
LOGIN_DATE_MAC DATE,
LOGIN_DATE_SNO DATE,
NO VARCHAR2(20)
)
tablespace YXDW
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64
next 8
minextents 1
maxextents unlimited
);
-- Add comments to the table
comment on table B_MAC_USB_KEY
is 'MAC和加密狗绑定关系表';
-- Add comments to the columns
comment on column B_MAC_USB_KEY.REMARK
is '备注';
comment on column B_MAC_USB_KEY.CREATE_BY
is '创建人';
comment on column B_MAC_USB_KEY.CREATE_DATE
is '创建时间';
comment on column B_MAC_USB_KEY.UPDATE_BY
is '修改人';
comment on column B_MAC_USB_KEY.UPDATE_DATE
is '修改时间';
comment on column B_MAC_USB_KEY.STATUS
is '0:正常1:删除';
comment on column B_MAC_USB_KEY.SNO
is '加密狗编号(usb外表的号码)';
comment on column B_MAC_USB_KEY.MAC
is '机器码';
comment on column B_MAC_USB_KEY.BIND_NUM
is '加密狗已绑定的MAC数量';
comment on column B_MAC_USB_KEY.LOGIN_DATE_MAC
is 'mac登录时间';
comment on column B_MAC_USB_KEY.LOGIN_DATE_SNO
is 'sno登录时间';
comment on column B_MAC_USB_KEY.NO
is 'SN,加密狗内部序列号';
SQL语句
参考:https://ask.csdn.net/questions/744504
SELECT * FROM (
SELECT user_id , login_time ,row_number() over (partition by user_id order by login_time desc) MM from xxx)
WHERE mm = 1
正式语句
<sql id="userUsbKeyColumns">
a.ID AS "id",
a.BRAND AS "brand", <!-- 终端品牌 -->
a.TYPE AS "type", <!-- 终端类型 -->
a.OWNER_ID AS "ownerId", <!-- 公司ID -->
(
select b.owner_name
from B_OWNER b
WHERE a.OWNER_ID = b.OWNER_ID
) AS "ownerName", <!-- 公司名 -->
a.USER_ID AS "userId", <!-- 使用人id -->
(select b.u_cname from sys_user b where b.USERID = a.USER_ID) AS "userName", <!-- 使用人姓名 -->
a.REMARK AS "remark",
a.CREATE_BY AS "createBy",
a.CREATE_DATE AS "createDate",
a.UPDATE_BY AS "updateBy",
a.UPDATE_DATE AS "updateDate",
a.STATUS AS "status", <!-- 0正常1停用 -->
a.SNO AS "sno", <!-- 加密狗设备编号(usb外表的号码) -->
a.NO AS "no", <!-- SN,加密狗内部序列号 -->
a.LOGIN_DATE_SNO AS "loginDateSno" <!-- 加密狗登录时间 -->
</sql>
SELECT
*
FROM
(
SELECT
<include refid="userUsbKeyColumns"/>
,ROW_NUMBER () OVER (
PARTITION BY a.user_id
ORDER BY a.LOGIN_DATE_SNO DESC
) RNO
FROM B_USER_USB_KEY a
<where>
a.STATUS = #{status}
<if test="ownerId != null and ownerId != '' ">
and a.OWNER_ID in (select owner_id from b_owner start with owner_id = #{ownerId} connect by prior owner_id=owner_pid)
</if>
<if test="sno != null and sno != '' ">
and a.sno = #{sno,jdbcType=VARCHAR}
</if>
<if test="userId != null and userId != '' ">
and a.USER_ID = #{userId}
</if>
</where>
)
WHERE
RNO= 1
注意:ROW_NUMBER () OVER前要加“,”。
分析
Partition By a.user_id是按照a.user_id对查询结果分割(Partition的意思就是分割),a.user_id相同的在一起
Order By ORDER BY a.LOGIN_DATE_SNO DESC是对分段的结果进行排序
对语句语句拆分分析
为了便于分析,现只查询上述语句的部分列
内层查询
SELECT
A . ID AS "id",
(
SELECT
b.owner_name
FROM
B_OWNER b
WHERE
A .OWNER_ID = b.OWNER_ID
) AS "ownerName",-- 公司名 -->
A .USER_ID AS userId, -- 用户id -->
(
SELECT
b.u_cname
FROM
sys_user b
WHERE
b.USERID = A .USER_ID
) AS "userName", -- 使用人姓名 -->
A .STATUS AS "status", -- 0正常1停用 -->
A .SNO AS "sno", -- 加密狗设备编号(usb外表的号码) -->
A . NO AS "no", -- SN,加密狗内部序列号 -->
A .LOGIN_DATE_SNO AS "loginDateSno" -- 加密狗登录时间 -->
,
ROW_NUMBER () OVER (
PARTITION BY A .user_id
ORDER BY
A .LOGIN_DATE_SNO DESC
) RNO
FROM
B_USER_USB_KEY A
WHERE
A .STATUS = '0'
结果
通过 ROW_NUMBER () OVER (
PARTITION BY A .user_id
ORDER BY
A .LOGIN_DATE_SNO DESC
) RNO
将user_id相同的放到一起,然后用LOGIN_DATE_SNO DESC进行排序,其中RNO表示user_id相同的记录有几个。每个user_id相同的截取片段都有一个第1名
如果不进行分割,即
ROW_NUMBER () OVER (
ORDER BY
A .LOGIN_DATE_SNO DESC
) RNO
中没有PARTITION BY A .user_id
则查询结果为
查询结果按登录时间倒序,只有1个第1名
外层语句
SELECT
*
FROM
(
内层语句
)
WHERE
RNO = 1
RNO=1表示只显示内层语句中user_id相同的数据块的第一条记录
结果
这部分可以参考:https://www.sohu.com/a/280125372_120045344
ORACLE的PARTITION BY 用法
是什么
Parttion by 关键字是Oracle中分析性函数over的一部分,Oracle从8.1.6开始提供分析函数,它和聚合函数不同的地方在于它能够返回一个分组中的多条记录,而聚合函数一般只有一条反映统计值的结果。
干什么
分析函数用于计算基于组的某种聚合值,常用于大型项目中的统计分析及相关功能
有那些
--row_number() 顺序排序
row_number() over(partition by ... order by ...)
--rank() (跳跃排序,如果有两个第一级别时,接下来是第三级别)
rank() over(partition by ... order by ...)
--dense_rank()(连续排序,如果有两个第一级别时,接下来是第二级)
dense_rank() over(partition by ... order by ...)
--求分组后的总数。
count() over(partition by ... order by ...)
--求分组后的最大值。
max() over(partition by ... order by ...)
--求分组后的最小值。
min() over(partition by ... order by ...)
sum() over(partition by ... order by ...)
--求分组后的平均值。
avg() over(partition by ... order by ...)
--第一个
first_value() over(partition by ... order by ...)
--最后一个
last_value() over(partition by ... order by ...)
--取出前n行数据。
lag() over(partition by ... order by ...)
--取出后n行数据。
lead() over(partition by ... order by ...)
--Ratio_to_report() 括号中就是分子,over() 括号中就是分母。
ratio_to_report() over(partition by ... order by ...)
percent_rank() over(partition by ... order by ...)
实例1
查询出每个部门工资最低的员工编号【每个部门可能有两个最低的工资员工】
create table TSALER
(
userid NUMBER(10),
salary NUMBER(10),
deptid NUMBER(10)
)
-- Add comments to the columns
comment on column TSALER.userid
is '员工ID';
comment on column TSALER.salary
is '工资';
comment on column TSALER.deptid
is '部门ID';
insert into TSALER (工号, 工资, 部门编号)
values (1, 200, 1);
insert into TSALER (工号, 工资, 部门编号)
values (2, 2000, 1);
insert into TSALER (工号, 工资, 部门编号)
values (3, 200, 1);
insert into TSALER (工号, 工资, 部门编号)
values (4, 1000, 2);
insert into TSALER (工号, 工资, 部门编号)
values (5, 1000, 2);
insert into TSALER (工号, 工资, 部门编号)
values (6, 3000, 2);
查询结果:
方法一
SELECT
tsaler.*
FROM
tsaler
INNER JOIN (
SELECT
MIN (salary) AS salary,
deptid
FROM
tsaler
GROUP BY
deptid
) c ON tsaler.salary = c.salary
AND tsaler.deptid = c.deptid
方法二
SELECT
*
FROM
tsaler
INNER JOIN (
SELECT
MIN (salary) AS salary,
deptid
FROM
tsaler
GROUP BY
deptid
) c USING (salary, deptid)
方法三
--row_number() 顺序排序
SELECT
ROW_NUMBER () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler;
--rank() (跳跃排序,如果有两个第一级别时,接下来是第三级别)
SELECT
RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler;
--dense_rank()(连续排序,如果有两个第一级别时,接下来是第二级)
SELECT
DENSE_RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler;
-------方案3解决方案
SELECT
*
FROM
(
SELECT
RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler
)
WHERE
my_rank = 1;
SELECT
*
FROM
(
SELECT
DENSE_RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler
)
WHERE
my_rank = 1;
参考:https://www.cnblogs.com/jak-black/p/4210653.html
实例2
需求:对每一个类型进行求和并且求该类型所占的比例
SELECT
T .CHANNEL AS PATTERN,
COUNT (T .TRANSACTIONKEY) AS T_COUNT,
SUM (T .AMT) AS T_AMT,
ROUND (
100 * SUM (T .AMT) / SUM (SUM(T .AMT)) OVER (PARTITION BY 1),
2
) AS AMT_PERCENT,
ROUND (
100 * COUNT (T .TRANSACTIONKEY) / SUM (COUNT(T .TRANSACTIONKEY)) OVER (PARTITION BY 1),
2
) AS COUNT_PERCENT
FROM
XX (表名) T
WHERE
T .PARTY_ID = '100579050'
GROUP BY
T .CHANNEL
实验数据
create table T2_TEMP(
NAME varchar2(10) primary key,
CLASS varchar2(10),
SROCE NUMBER
)
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('cfe', '2', 74);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('dss', '1', 95);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('ffd', '1', 95);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('fda', '1', 80);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('gds', '2', 92);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('gf', '3', 99);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('ddd', '3', 99);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('adf', '3', 45);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('asdf', '3', 55);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('3dd', '3', 78);
1、over函数的写法:
over(partition by class order by sroce) 按照sroce排序进行累计,order by是个默认的开窗函数,按照class分区。
2、开窗的窗口范围:
over(order by sroce range between 5 preceding and 5 following):窗口范围为当前行数据幅度减5加5后的范围内的。
over(order by sroce rows between 5 preceding and 5 following):窗口范围为当前行前后各移动5行。
3、与over()函数结合的函数的介绍
(1)、查询每个班的第一名的成绩:如下
SELECT
*
FROM
(
SELECT
T . NAME,
T . CLASS,
T .sroce,
RANK () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T
)
WHERE
mm = 1;
结果为:
dss 1 95 1
ffd 1 95 1
gds 2 92 1
gf 3 99 1
ddd 3 99 1
注意:在求第一名成绩的时候,不能用row_number(),因为如果同班有两个并列第一,row_number()只返回一个结果。
SELECT
*
FROM
(
SELECT
T . NAME,
T . CLASS,
T .sroce,
ROW_NUMBER () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T
)
WHERE
mm = 1;
结果为:
dss 1 95 1
gfs 2 92 1
ddd 3 99 1
可以看出,本来第一名是两个人的并列,结果只显示了一个。
(2)、rank()和dense_rank()可以将所有的都查找出来,rank可以将并列第一名的都查找出来;rank()和dense_rank()区别:rank()是跳跃排序,有两个第二名时接下来就是第四名。
求班级成绩排名:
SELECT
T . NAME,
T . CLASS,
T .sroce,
RANK () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
查询结果:
dss 1 95 1
ffd 1 95 1
fda 1 80 3
gds 2 92 1
cfe 2 74 2
gf 3 99 1
ddd 3 99 1
3dd 3 78 3
asdf 3 55 4
adf 3 45 5
dense_rank()是连续排序,有两个第二名时仍然跟着第三名
SELECT
T . NAME,
T . CLASS,
T .sroce,
DENSE_RANK () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
查询结果:
dss 1 95 1
ffd 1 95 1
fda 1 80 2
gds 2 92 1
cfe 2 74 2
gf 3 99 1
ddd 3 99 1
3dd 3 78 2
asdf 3 55 3
adf 3 45 4
3、sum()over()的使用
根据班级进行分数求和
SELECT
T . NAME,
T . CLASS,
T .sroce,
SUM (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
dss 1 95 190 --由于两个95都是第一名,所以累加时是两个第一名的相加
ffd 1 95 190
fda 1 80 270 --第一名加上第二名的
gds 2 92 92
cfe 2 74 166
gf 3 99 198
ddd 3 99 198
3dd 3 78 276
asdf 3 55 331
adf 3 45 376
4、first_value() over()和last_value() over()的使用
SELECT
T . NAME,
T . CLASS,
T .sroce,
FIRST_VALUE (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
SELECT
T . NAME,
T . CLASS,
T .sroce,
LAST_VALUE (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
分别求出第一个和最后一个成绩。
5、sum() over()的使用
SELECT
T . NAME,
T . CLASS,
T .sroce,
SUM (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
求出班级的总分。
6、over partition by与group by的区别:
group by是对检索结果的保留行进行单纯分组,一般和聚合函数一起使用例如max、min、sum、avg、count等一块用。partition by虽然也具有分组功能,但同时也具有其他的高级功能。